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
6 changes: 6 additions & 0 deletions .changes/unreleased/added-20260618-043404.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
kind: added
body: Supports SQL db creation with initial properties
time: 2026-06-18T04:34:04.941603586Z
custom:
Author: v-alexmoraru
AuthorLink: https://github.com/v-alexmoraru
13 changes: 13 additions & 0 deletions src/fabric_cli/core/fab_constant.py
Original file line number Diff line number Diff line change
Expand Up @@ -352,3 +352,16 @@
# Invalid query parameters for set command across all fabric resources
SET_COMMAND_INVALID_QUERIES = ["id", "type", "workspaceId", "folderId"]

# SQLDatabase property validation
SQL_DATABASE_BACKUP_RETENTION_MIN_DAYS = 1
SQL_DATABASE_BACKUP_RETENTION_MAX_DAYS = 35

# SQLDatabase creation modes
SQL_DATABASE_CREATION_MODE_NEW = "New"
SQL_DATABASE_CREATION_MODE_RESTORE = "Restore"
SQL_DATABASE_CREATION_MODE_RESTORE_DELETED = "RestoreDeletedDatabase"
SQL_DATABASE_VALID_CREATION_MODES = [
SQL_DATABASE_CREATION_MODE_NEW,
SQL_DATABASE_CREATION_MODE_RESTORE,
SQL_DATABASE_CREATION_MODE_RESTORE_DELETED,
]
22 changes: 22 additions & 0 deletions src/fabric_cli/errors/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -264,3 +264,25 @@ def unsupported_parameter(key: str) -> str:
@staticmethod
def invalid_parameter_format(param: str) -> str:
return f"Invalid parameter format: '{param}'. Use key=value or key!=value."

@staticmethod
def invalid_backup_retention_days(value: str, min_days: int, max_days: int) -> str:
return (
f"Invalid backupRetentionDays value '{value}'. "
f"Must be an integer between {min_days} and {max_days} days."
)

@staticmethod
def invalid_sql_database_creation_mode(mode: str, valid_modes: list) -> str:
return (
f"Invalid creation mode '{mode}'. "
f"Valid modes are: {', '.join(valid_modes)}."
)

@staticmethod
def sql_database_property_not_allowed_for_mode(
property_name: str, mode: str
) -> str:
return (
f"Property '{property_name}' is not allowed when using creation mode '{mode}'."
)
85 changes: 85 additions & 0 deletions src/fabric_cli/utils/fab_cmd_mkdir_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from fabric_cli.core.fab_types import ItemType
from fabric_cli.core.hiearchy.fab_hiearchy import Item
from fabric_cli.errors import ErrorMessages
from fabric_cli.errors.common import CommonErrors
from fabric_cli.utils import fab_ui as utils_ui


Expand Down Expand Up @@ -218,6 +219,11 @@ def add_type_specific_payload(item: Item, args, payload):
]
}

case ItemType.SQL_DATABASE:
creation_payload = _build_sql_database_creation_payload(params)
if creation_payload:
payload_dict["creationPayload"] = creation_payload

return payload_dict


Expand Down Expand Up @@ -344,6 +350,8 @@ def get_params_per_item_type(item: Item):
case ItemType.MOUNTED_DATA_FACTORY:
required_params = ["subscriptionId",
"resourceGroup", "factoryName"]
case ItemType.SQL_DATABASE:
optional_params = ["mode", "backupRetentionDays", "collation"]

return required_params, optional_params

Expand Down Expand Up @@ -791,6 +799,83 @@ def lowercase_keys(data):
return data


def _build_sql_database_creation_payload(params: dict) -> dict | None:
"""Build the creationPayload for SQLDatabase creation.

Supports 'New' mode (default) with backupRetentionDays and collation properties.
Restore modes do not accept these properties.

Returns None if no SQLDatabase-specific params provided.
"""
mode = params.get("mode")
backup_retention_days = params.get("backupretentiondays")
collation = params.get("collation")

if mode is None and backup_retention_days is None and collation is None:
return None

# Resolve and validate mode
if mode:
matched_mode = next(
(
m for m in fab_constant.SQL_DATABASE_VALID_CREATION_MODES
if m.lower() == mode.lower()
),
None
)
if not matched_mode:
raise FabricCLIError(
CommonErrors.invalid_sql_database_creation_mode(
mode, fab_constant.SQL_DATABASE_VALID_CREATION_MODES
),
fab_constant.ERROR_INVALID_INPUT,
)
mode = matched_mode
else:
mode = fab_constant.SQL_DATABASE_CREATION_MODE_NEW

# Validate properties are only used with 'New' mode
if mode != fab_constant.SQL_DATABASE_CREATION_MODE_NEW:
for prop_name, prop_value in [
("backupRetentionDays", backup_retention_days),
("collation", collation)
]:
if prop_value is not None:
raise FabricCLIError(
CommonErrors.sql_database_property_not_allowed_for_mode(
prop_name, mode
),
fab_constant.ERROR_INVALID_INPUT,
)

creation_payload: dict = {"creationMode": mode}

if backup_retention_days is not None:
_validate_backup_retention_days(backup_retention_days)
creation_payload["backupRetentionDays"] = int(backup_retention_days)

if collation is not None:
creation_payload["collation"] = collation

return creation_payload


def _validate_backup_retention_days(value: str) -> None:
"""Validate backupRetentionDays is an integer within 1-35 range."""
min_days = fab_constant.SQL_DATABASE_BACKUP_RETENTION_MIN_DAYS
max_days = fab_constant.SQL_DATABASE_BACKUP_RETENTION_MAX_DAYS

try:
int_value = int(value)
if not min_days <= int_value <= max_days:
raise ValueError()
except (ValueError, TypeError):
raise FabricCLIError(
CommonErrors.invalid_backup_retention_days(str(value), min_days, max_days),
fab_constant.ERROR_INVALID_INPUT,
)


def validate_spark_pool_params(params):
# Node size options
allowed_node_sizes = {"small", "medium", "large", "xlarge", "xxlarge"}
Expand Down
223 changes: 223 additions & 0 deletions tests/test_utils/test_fab_cmd_mkdir_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -205,3 +205,226 @@ def test_find_mpe_connection_return_403_success(self):
called_url = call_args.args[1] if len(call_args.args) > 1 else call_args.kwargs['url']
assert "privateEndpointConnections" in called_url
assert "api-version=2023-11-01" in called_url


# SQLDatabase creation payload tests
class TestBuildSqlDatabaseCreationPayload:
"""Test cases for _build_sql_database_creation_payload function."""

def test_build_sql_database_creation_payload_no_params_returns_none(self):
"""Test that no creationPayload is returned when no SQLDatabase params provided."""
from fabric_cli.utils.fab_cmd_mkdir_utils import _build_sql_database_creation_payload

result = _build_sql_database_creation_payload({})
assert result is None

def test_build_sql_database_creation_payload_unrelated_params_returns_none(self):
"""Test that no creationPayload is returned when unrelated params provided."""
from fabric_cli.utils.fab_cmd_mkdir_utils import _build_sql_database_creation_payload

result = _build_sql_database_creation_payload({"description": "test"})
assert result is None

def test_build_sql_database_creation_payload_mode_new_explicit_success(self):
"""Test SQLDatabase creation with explicit mode=New."""
from fabric_cli.utils.fab_cmd_mkdir_utils import _build_sql_database_creation_payload

params = {"mode": "new"}
result = _build_sql_database_creation_payload(params)

assert result is not None
assert result["creationMode"] == "New"

def test_build_sql_database_creation_payload_backup_retention_days_success(self):
"""Test SQLDatabase creation with backupRetentionDays infers mode=New."""
from fabric_cli.utils.fab_cmd_mkdir_utils import _build_sql_database_creation_payload

params = {"backupretentiondays": "21"}
result = _build_sql_database_creation_payload(params)

assert result is not None
assert result["creationMode"] == "New"
assert result["backupRetentionDays"] == 21

def test_build_sql_database_creation_payload_collation_success(self):
"""Test SQLDatabase creation with collation infers mode=New."""
from fabric_cli.utils.fab_cmd_mkdir_utils import _build_sql_database_creation_payload

params = {"collation": "SQL_Latin1_General_CP1_CI_AS"}
result = _build_sql_database_creation_payload(params)

assert result is not None
assert result["creationMode"] == "New"
assert result["collation"] == "SQL_Latin1_General_CP1_CI_AS"

def test_build_sql_database_creation_payload_both_properties_success(self):
"""Test SQLDatabase creation with both backupRetentionDays and collation."""
from fabric_cli.utils.fab_cmd_mkdir_utils import _build_sql_database_creation_payload

params = {
"mode": "New",
"backupretentiondays": "7",
"collation": "Latin1_General_100_CI_AS_KS_WS_SC_UTF8",
}
result = _build_sql_database_creation_payload(params)

assert result is not None
assert result["creationMode"] == "New"
assert result["backupRetentionDays"] == 7
assert result["collation"] == "Latin1_General_100_CI_AS_KS_WS_SC_UTF8"

def test_build_sql_database_creation_payload_mode_case_insensitive_success(self):
"""Test that mode parameter is case-insensitive."""
from fabric_cli.utils.fab_cmd_mkdir_utils import _build_sql_database_creation_payload

params = {"mode": "NEW", "backupretentiondays": "14"}
result = _build_sql_database_creation_payload(params)

assert result is not None
assert result["creationMode"] == "New"
assert result["backupRetentionDays"] == 14

def test_build_sql_database_creation_payload_backup_retention_min_success(self):
"""Test backupRetentionDays with minimum value (1)."""
from fabric_cli.utils.fab_cmd_mkdir_utils import _build_sql_database_creation_payload

params = {"backupretentiondays": "1"}
result = _build_sql_database_creation_payload(params)

assert result["backupRetentionDays"] == 1

def test_build_sql_database_creation_payload_backup_retention_max_success(self):
"""Test backupRetentionDays with maximum value (35)."""
from fabric_cli.utils.fab_cmd_mkdir_utils import _build_sql_database_creation_payload

params = {"backupretentiondays": "35"}
result = _build_sql_database_creation_payload(params)

assert result["backupRetentionDays"] == 35

def test_build_sql_database_creation_payload_backup_retention_out_of_range_failure(
self,
):
"""Test that backupRetentionDays outside 1-35 range raises error."""
from fabric_cli.utils.fab_cmd_mkdir_utils import _build_sql_database_creation_payload

params = {"backupretentiondays": "36"}
with pytest.raises(FabricCLIError) as exc_info:
_build_sql_database_creation_payload(params)

assert exc_info.value.status_code == fab_constant.ERROR_INVALID_INPUT
assert "36" in str(exc_info.value.message)
assert "1" in str(exc_info.value.message)
assert "35" in str(exc_info.value.message)

def test_build_sql_database_creation_payload_backup_retention_zero_failure(self):
"""Test that backupRetentionDays of 0 raises error."""
from fabric_cli.utils.fab_cmd_mkdir_utils import _build_sql_database_creation_payload

params = {"backupretentiondays": "0"}
with pytest.raises(FabricCLIError) as exc_info:
_build_sql_database_creation_payload(params)

assert exc_info.value.status_code == fab_constant.ERROR_INVALID_INPUT

def test_build_sql_database_creation_payload_backup_retention_not_integer_failure(
self,
):
"""Test that non-integer backupRetentionDays raises error."""
from fabric_cli.utils.fab_cmd_mkdir_utils import _build_sql_database_creation_payload

params = {"backupretentiondays": "seven"}
with pytest.raises(FabricCLIError) as exc_info:
_build_sql_database_creation_payload(params)

assert exc_info.value.status_code == fab_constant.ERROR_INVALID_INPUT
assert "seven" in str(exc_info.value.message)

def test_build_sql_database_creation_payload_backup_retention_negative_failure(
self,
):
"""Test that negative backupRetentionDays raises error."""
from fabric_cli.utils.fab_cmd_mkdir_utils import _build_sql_database_creation_payload

params = {"backupretentiondays": "-5"}
with pytest.raises(FabricCLIError) as exc_info:
_build_sql_database_creation_payload(params)

assert exc_info.value.status_code == fab_constant.ERROR_INVALID_INPUT

def test_build_sql_database_creation_payload_invalid_mode_failure(self):
"""Test that invalid creation mode raises error."""
from fabric_cli.utils.fab_cmd_mkdir_utils import _build_sql_database_creation_payload

params = {"mode": "InvalidMode"}
with pytest.raises(FabricCLIError) as exc_info:
_build_sql_database_creation_payload(params)

assert exc_info.value.status_code == fab_constant.ERROR_INVALID_INPUT
assert "InvalidMode" in str(exc_info.value.message)
assert "New" in str(exc_info.value.message)
assert "Restore" in str(exc_info.value.message)

def test_build_sql_database_creation_payload_restore_mode_no_properties_success(
self,
):
"""Test Restore mode without backupRetentionDays or collation is valid."""
from fabric_cli.utils.fab_cmd_mkdir_utils import _build_sql_database_creation_payload

params = {"mode": "Restore"}
result = _build_sql_database_creation_payload(params)

assert result is not None
assert result["creationMode"] == "Restore"
assert "backupRetentionDays" not in result
assert "collation" not in result

def test_build_sql_database_creation_payload_restore_mode_backup_retention_failure(
self,
):
"""Test that backupRetentionDays is not allowed in Restore mode."""
from fabric_cli.utils.fab_cmd_mkdir_utils import _build_sql_database_creation_payload

params = {"mode": "Restore", "backupretentiondays": "21"}
with pytest.raises(FabricCLIError) as exc_info:
_build_sql_database_creation_payload(params)

assert exc_info.value.status_code == fab_constant.ERROR_INVALID_INPUT
assert "backupRetentionDays" in str(exc_info.value.message)
assert "Restore" in str(exc_info.value.message)

def test_build_sql_database_creation_payload_restore_mode_collation_failure(self):
"""Test that collation is not allowed in Restore mode."""
from fabric_cli.utils.fab_cmd_mkdir_utils import _build_sql_database_creation_payload

params = {"mode": "Restore", "collation": "SQL_Latin1_General_CP1_CI_AS"}
with pytest.raises(FabricCLIError) as exc_info:
_build_sql_database_creation_payload(params)

assert exc_info.value.status_code == fab_constant.ERROR_INVALID_INPUT
assert "collation" in str(exc_info.value.message)
assert "Restore" in str(exc_info.value.message)

def test_build_sql_database_creation_payload_restore_deleted_mode_success(self):
"""Test RestoreDeletedDatabase mode is valid."""
from fabric_cli.utils.fab_cmd_mkdir_utils import _build_sql_database_creation_payload

params = {"mode": "RestoreDeletedDatabase"}
result = _build_sql_database_creation_payload(params)

assert result is not None
assert result["creationMode"] == "RestoreDeletedDatabase"

def test_build_sql_database_creation_payload_restore_deleted_mode_properties_failure(
self,
):
"""Test that properties are not allowed in RestoreDeletedDatabase mode."""
from fabric_cli.utils.fab_cmd_mkdir_utils import _build_sql_database_creation_payload

params = {"mode": "RestoreDeletedDatabase", "backupretentiondays": "7"}
with pytest.raises(FabricCLIError) as exc_info:
_build_sql_database_creation_payload(params)

assert exc_info.value.status_code == fab_constant.ERROR_INVALID_INPUT
assert "backupRetentionDays" in str(exc_info.value.message)
assert "RestoreDeletedDatabase" in str(exc_info.value.message)
Loading