Feature request: Add analogue of `nameof` expression from C# for better reference control to SSDT/DacFx
- Dominant language
- C#
- Stars
- 460
- Forks
- 29
- Avg merge
- 4d 9h
- Merged PRs (30d)
- 7
Description
**Is your feature request related to a problem? Please describe.**
Sometimes we reference objects by their name defined as a string literal. When someone renames the object or removes it from the project string literals don't get validated by any mechanism and we get inconsistency. Some filters become useless, dynamic SQL gets broken, error messages now mention something non-existent. Below are some examples where string literals meaning object names should be evaluated/validated at compile time:
### Searching for SB conversations or messages
```SQL
SELECT 1
FROM sys.conversation_endpoints AS ce
INNER JOIN sys.services AS s
ON ce.service_id = s.service_id
INNER JOIN sys.service_contracts AS sc
ON ce.service_contract_id = sc.service_contract_id
WHERE ce.state NOT IN ('ER', 'CD', 'DO', 'DI')
AND s.name = 'My_InitiatorService'
AND ce.far_service = 'My_TargetService'
AND sc.name = 'My_Contract'
AND ce.conversation_handle = @chandle
```
Here
- My_InitiatorService must be defined as CREATE SERVICE in the project
- My_Contract must be defined as CREATE CONTRACT in the project
If any of mentioned objects are renamed the code will compile successfully, deployment will complete with no errors but after this deployment given select will never return anything again since it has become incompatible with the data model.
### Testing with tSQLt Framework
```SQL
EXEC tSQLt.FakeTableData @TableName = N'dbo.faked_table';
EXEC tSQLt.ApplyTrigger
@TableName = N'dbo.faked_table'
, @TriggerName = 'dbo.my_trigger_after_insert';
EXEC tSQLt.SpyProcedure @ProcedureName = N'dbo.faked_procedure';
```
Here
- dbo.faked_table must be defined as CREATE TABLE in the project
- faked_procedure must be defined as CREATE PROCEDURE in the project
- my_trigger_after_insert must be defined as CREATE TRIGGER in the project
If any of mentioned objects are renamed or dropped the tests will compile successfully, deploy will complete with no errors but tests will fail because referenced objects don't exist.
### Implementing EventBus pattern with dynamic SQL
```SQL
DECLARE @handler_proc SYSNAME, @event_body JSON
SELECT TOP(1)
@handler_proc =
CASE e.event_type
WHEN 'A' THEN 'dbo.my_event_handler_for_A'
WHEN 'B' THEN 'dbo.my_event_handler_for_B'
ELSE 'dbo.default_handler'
END,
@event_body = e.event_body
FROM dbo.some_events e
ORDER BY dt_insert ASC
EXEC @handler_proc
@event_body = @event_body
```
Here
- dbo.my_event_handler_for_A, dbo.my_event_handler_for_B, dbo.default_handler must be defined as CREATE PROCEDURE in the project
If any of mentioned procedures are renamed or dropped the procedure containing given dynamic code will compile successfully, deployment will complete with no errors but the code will fail at runtime because referenced objects don't exist.
### Describing exceptions
```SQL
CREATE PROCEDURE dbo.some_proc
@important_argument VARCHAR(10)
AS
BEGIN
DECLARE @res INT
IF NULLIF(@important_argument, '') IS NULL
RAISERROR('@important_argument must not be empty', 16, 1)
EXEC @res = dbo.my_proc
@arg = @important_argument
IF @res <> 0
RAISERROR('dbo.my_proc returned unexpected result: %d', 16, 1, @res)
END
```
Here
- if parameter @important_argument is renamed or removed then the first RAISERROR would give a misleading description
- if dbo.my_proc is renamed of replaced with something else then the second RAISERROR would give a misleading description
If any of mentioned items gets renamed then a developer will get a warning or error message related to direct references only, but will not get any notifications about mentions inside string literals.
---
**Describe the solution you'd like**
In C# there is a [nameof](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/nameof) expression
> A nameof expression produces the name of a variable, type, or member as the string constant. A nameof expression is evaluated at compile time and has no effect at run time.
which helps controlling references to types, variables, arguments, classes and methods. If a referenced, say, method `BestMethod` gets renamed to `SomeNiceMethod`, one will get a _compile-time_ error at the place where `nameof(BestMethod)` is called. If the reference was defined as a string literal `'BestMethod'` then there'd be no compile-time error and it'd be hard to detect such inconsistency.
_It'd be great if we had something similar in SQL projects._
The solution could look like this:
```SQL
SELECT 1
FROM sys.conversation_endpoints AS ce
INNER JOIN sys.services AS s
ON ce.service_id = s.service_id
INNER JOIN sys.service_contracts AS sc
ON ce.service_contract_id = sc.service_contract_id
WHERE ce.state NOT IN ('ER', 'CD', 'DO', 'DI')
AND s.name = [$nameof(My_InitiatorService)]
AND ce.far_service = 'My_TargetService'
AND sc.name = [$nameof(My_Contract)]
AND ce.conversation_handle = @chandle
GO
-- without required quotation
EXEC tSQLt.FakeTableData @TableName = [$nameof(dbo.faked_table)]
-- or with quotes
EXEC tSQLt.ApplyTrigger
@TableName = N'[$nameof(dbo.faked_table)]'
, @TriggerName = N'[$nameof(dbo.my_trigger_after_insert)]'
EXEC tSQLt.SpyProcedure @ProcedureName = [$nameof(dbo.faked_procedure)];
GO
DECLARE @handler_proc SYSNAME, @event_body JSON
SELECT TOP(1)
@handler_proc =
CASE e.event_type
WHEN 'A' THEN [$nameof(dbo.my_event_handler_for_A)]
WHEN 'B' THEN [$nameof(dbo.my_event_handler_for_B)]
ELSE [$nameof(dbo.default_handler)]
END,
@event_body = e.event_body
FROM dbo.some_events e
ORDER BY dt_insert ASC
EXEC @handler_proc
@event_body = @event_body
GO
CREATE PROCEDURE dbo.some_proc
@important_argument VARCHAR(10)
AS
BEGIN
DECLARE @res INT
IF NULLIF(@important_argument, '') IS NULL
RAISERROR('[$nameof(@important_argument)] must not be empty', 16, 1)
EXEC @res = dbo.my_proc
@arg = @important_argument
IF @res <> 0
RAISERROR('[$nameof(dbo.my_proc)] returned unexpected result: %d', 16, 1, @res)
END
```
Maybe with less square brackets and dollars, more like _syntactical sugar_.
IMO also `F12` (Go to definition) should work in such object mentions.
---
**Describe alternatives you've considered**
Making a custom solution based on ScriptDom and heuristic knowledge about some scenarios where a string literal must match an existing object from the project, including it as one of msbuild targets into compile process (kind of additional _code analyzer_).
There is also a thing called **SqlCmd Variables** but they must be defined explicitly in sqlproj, in publish.xml which makes it hard to maintain them. To keep object definition and all incoming referenced as string literals consistent we'd have to use `[$(cmd variable)]` in object `CREATE` script as well which feels unwise and would make it hard to maintain both: object definition and references.
Contributor guide
Research direction
No repository files or tests are named. Start by locating the SQL project compilation and name-resolution entry points, then review the ScriptDom and MSBuild analyzer alternatives described in the issue. Done would require an agreed syntax and compile-time validation behavior for string references, including the listed SQL examples.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- csharp, sql
- Domain
- compilers, databases
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 25/100