Skip to content
Open
Show file tree
Hide file tree
Changes from all 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 extensions/mssql/l10n/bundle.l10n.json
Original file line number Diff line number Diff line change
Expand Up @@ -1194,6 +1194,7 @@
"Operation failed": "Operation failed",
"An unexpected error occurred": "An unexpected error occurred",
"Failed to load databases": "Failed to load databases",
"Loading databases...": "Loading databases...",
"DACPAC deployed successfully": "DACPAC deployed successfully",
"DACPAC extracted successfully": "DACPAC extracted successfully",
"BACPAC imported successfully": "BACPAC imported successfully",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -596,24 +596,47 @@ export class DacpacDialogWebviewController extends ReactWebviewPanelController<
/**
* Lists databases on the connected server
*/
private async listDatabases(ownerUri: string): Promise<{ databases: string[] }> {
private async listDatabases(
ownerUri: string,
): Promise<{ databases: string[]; errorMessage?: string }> {
const systemDatabases = ["master", "tempdb", "model", "msdb"];
const stateDatabaseName = this.state.databaseName;

let errorMessage: string | undefined;
try {
const result = await this.connectionManager.client.sendRequest(
ListDatabasesRequest.type,
{ ownerUri: ownerUri },
);
const databaseNames = await this.connectionManager.listDatabases(ownerUri);

// Filter out system databases
const systemDatabases = ["master", "tempdb", "model", "msdb"];
const userDatabases = (result.databaseNames || []).filter(
const userDatabases = (databaseNames || []).filter(
(db) => !systemDatabases.includes(db.toLowerCase()),
);

return { databases: userDatabases };
if (userDatabases.length > 0) {
// Ensure the state database is in the list if set
if (
stateDatabaseName &&
!systemDatabases.includes(stateDatabaseName.toLowerCase()) &&
!userDatabases.includes(stateDatabaseName)
) {
userDatabases.unshift(stateDatabaseName);
}
return { databases: userDatabases };
}
} catch (error) {
this.logger.error(`Failed to list databases: ${error}`);
return { databases: [] };
errorMessage = error instanceof Error ? error.message : "Failed to list databases";
}

// Fallback: if the database list is empty or the request failed,
// use the database name from the initial state (set via ObjectExplorerUtils.getDatabaseName)
if (stateDatabaseName && !systemDatabases.includes(stateDatabaseName.toLowerCase())) {
return {
databases: [stateDatabaseName],
errorMessage,
};
}

return { databases: [], errorMessage };
}

/**
Expand Down Expand Up @@ -803,6 +826,7 @@ export class DacpacDialogWebviewController extends ReactWebviewPanelController<
isConnected: boolean;
errorMessage?: string;
isFabric?: boolean;
databaseName?: string;
}> {
try {
// Find the profile in saved connections
Expand All @@ -824,6 +848,7 @@ export class DacpacDialogWebviewController extends ReactWebviewPanelController<

// Check if this is a Fabric connection
const isFabric = this.isFabricConnection(profile as IConnectionDialogProfile);
const databaseName = profile.database || "";

// Check if already connected and the connection is valid
let ownerUri = this.connectionManager.getUriForConnection(profile);
Expand All @@ -833,6 +858,7 @@ export class DacpacDialogWebviewController extends ReactWebviewPanelController<
ownerUri,
isConnected: true,
isFabric,
databaseName,
};
}

Expand All @@ -848,6 +874,7 @@ export class DacpacDialogWebviewController extends ReactWebviewPanelController<
ownerUri,
isConnected: true,
isFabric,
databaseName,
};
} else {
// Check if connection failed due to error or if it was never initiated
Expand All @@ -862,6 +889,7 @@ export class DacpacDialogWebviewController extends ReactWebviewPanelController<
isConnected: false,
errorMessage,
isFabric,
databaseName,
};
}
} catch (error) {
Expand Down Expand Up @@ -920,6 +948,13 @@ export class DacpacDialogWebviewController extends ReactWebviewPanelController<
return { isValid: false, errorMessage };
}

// Check if the database matches the one from the active connection.
// If the user lacks permissions to list databases but is connected to a specific database,
// we should trust the connection's database and skip the server-side existence check.
const isConnectionDatabase =
this.state.databaseName &&
databaseName.toLowerCase() === this.state.databaseName.toLowerCase();

// Check if database exists
try {
const result = await this.connectionManager.client.sendRequest(
Expand Down Expand Up @@ -951,6 +986,11 @@ export class DacpacDialogWebviewController extends ReactWebviewPanelController<

// For Extract/Export operations, database must exist
if (!shouldNotExist && !exists) {
// If the database matches the connection's database, trust it even if
// the list is incomplete (user may lack permissions to list all databases)
if (isConnectionDatabase) {
return { isValid: true };
}
return {
isValid: false,
errorMessage: LocConstants.DacpacDialog.DatabaseNotFound,
Expand All @@ -959,6 +999,11 @@ export class DacpacDialogWebviewController extends ReactWebviewPanelController<

return { isValid: true };
} catch (error) {
// If listing databases failed but the database matches the connection's database,
// allow the operation since the user is already connected to this database
if (isConnectionDatabase) {
return { isValid: true };
}
const errorMessage =
error instanceof Error
? `Failed to validate database name: ${error.message}`
Expand Down
1 change: 1 addition & 0 deletions extensions/mssql/src/reactviews/common/locConstants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1827,6 +1827,7 @@ export class LocConstants {
operationFailed: l10n.t("Operation failed"),
unexpectedError: l10n.t("An unexpected error occurred"),
failedToLoadDatabases: l10n.t("Failed to load databases"),
loadingDatabases: l10n.t("Loading databases..."),
deploySuccess: l10n.t("DACPAC deployed successfully"),
extractSuccess: l10n.t("DACPAC extracted successfully"),
importSuccess: l10n.t("BACPAC imported successfully"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { Dropdown, Field, Input, makeStyles, Option } from "@fluentui/react-components";
import { Dropdown, Field, Input, makeStyles, Option, Spinner } from "@fluentui/react-components";
import { locConstants } from "../../common/locConstants";

/**
Expand All @@ -19,11 +19,13 @@ interface SourceDatabaseSectionProps {
setDatabaseName: (value: string) => void;
availableDatabases: string[];
isOperationInProgress: boolean;
isLoadingDatabases: boolean;
ownerUri: string;
validationMessages: Record<string, ValidationMessage>;
showDatabaseSource: boolean;
showNewDatabase: boolean;
isFabric?: boolean;
onDropdownOpen?: () => void;
}

const useStyles = makeStyles({
Expand All @@ -39,11 +41,13 @@ export const SourceDatabaseSection = ({
setDatabaseName,
availableDatabases,
isOperationInProgress,
isLoadingDatabases,
ownerUri,
validationMessages,
showDatabaseSource,
showNewDatabase,
isFabric = false,
onDropdownOpen,
}: SourceDatabaseSectionProps) => {
const classes = useStyles();

Expand All @@ -67,14 +71,30 @@ export const SourceDatabaseSection = ({
placeholder={locConstants.dacpacDialog.selectDatabase}
value={databaseName}
selectedOptions={[databaseName]}
onOptionSelect={(_, data) => setDatabaseName(data.optionText || "")}
onOptionSelect={(_, data) => {
if (!isLoadingDatabases) {
setDatabaseName(data.optionText || "");
}
}}
onOpenChange={(_, data) => {
if (data.open && onDropdownOpen) {
onDropdownOpen();
}
}}
disabled={isOperationInProgress || !ownerUri || isFabric}
aria-label={locConstants.dacpacDialog.sourceDatabaseLabel}>
{availableDatabases.map((db) => (
<Option key={db} value={db}>
{db}
{isLoadingDatabases ? (
<Option key="__loading__" value="" text="" disabled>
<Spinner size="extra-tiny" />{" "}
{locConstants.dacpacDialog.loadingDatabases}
</Option>
))}
) : (
availableDatabases.map((db) => (
<Option key={db} value={db}>
{db}
</Option>
))
)}
</Dropdown>
</Field>
) : (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
Option,
Radio,
RadioGroup,
Spinner,
} from "@fluentui/react-components";
import { locConstants } from "../../common/locConstants";

Expand All @@ -30,9 +31,11 @@ interface TargetDatabaseSectionProps {
setIsNewDatabase: (value: boolean) => void;
availableDatabases: string[];
isOperationInProgress: boolean;
isLoadingDatabases: boolean;
ownerUri: string;
validationMessages: Record<string, ValidationMessage>;
isFabric?: boolean;
onDropdownOpen?: () => void;
}

const useStyles = makeStyles({
Expand All @@ -55,9 +58,11 @@ export const TargetDatabaseSection = ({
setIsNewDatabase,
availableDatabases,
isOperationInProgress,
isLoadingDatabases,
ownerUri,
validationMessages,
isFabric = false,
onDropdownOpen,
}: TargetDatabaseSectionProps) => {
const classes = useStyles();

Expand Down Expand Up @@ -117,14 +122,30 @@ export const TargetDatabaseSection = ({
placeholder={locConstants.dacpacDialog.selectDatabase}
value={databaseName}
selectedOptions={[databaseName]}
onOptionSelect={(_, data) => setDatabaseName(data.optionText || "")}
onOptionSelect={(_, data) => {
if (!isLoadingDatabases) {
setDatabaseName(data.optionText || "");
}
}}
onOpenChange={(_, data) => {
if (data.open && onDropdownOpen) {
onDropdownOpen();
}
}}
disabled={isOperationInProgress || !ownerUri}
aria-label={locConstants.dacpacDialog.databaseNameLabel}>
{availableDatabases.map((db) => (
<Option key={db} value={db}>
{db}
{isLoadingDatabases ? (
<Option key="__loading__" value="" text="" disabled>
<Spinner size="extra-tiny" />{" "}
{locConstants.dacpacDialog.loadingDatabases}
</Option>
))}
) : (
availableDatabases.map((db) => (
<Option key={db} value={db}>
{db}
</Option>
))
)}
</Dropdown>
</Field>
)}
Expand Down
Loading
Loading