Skip to content

Commit 311e42c

Browse files
authored
Merge pull request #46 from Azure-Samples/dev/stuartpa/tsql-agent-mode-sample
VSCode Agent Mode sample for T/SQL applications
2 parents 50f0609 + b6ec138 commit 311e42c

17 files changed

+982
-0
lines changed
Lines changed: 277 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,277 @@
1+
2+
# Guidelines for AI Assistants: Scaffolding SQL Database Projects for T/SQL Applications
3+
4+
This notebook guides AI assistants through a step-by-step chain-of-thought to generate a full SQL Database Application using the SQL project SDK, Microsoft.Build.Sql
5+
6+
Each database object **must** be placed in its own `.sql` file, organized into a coherent folder structure. (this makes it easier for VSCode agent-mode to work with files)
7+
8+
The application should expose Stored Procedures and Views for all operations (all tables should be hidden as internal to the application).
9+
10+
NOTES:
11+
- You (the AI Agent) **must not** edit this file.
12+
13+
## Summary
14+
15+
- Use SQL Database Projects (.sqlproj) to manage schema as code. (Target SQL Server 2019, as this is the latest version that tSQLt supports via a .dacpac)
16+
- You can find the full documentation for `Sql Database Projects` here:
17+
- https://raw.githubusercontent.com/MicrosoftDocs/sql-docs/refs/heads/live/docs/tools/sql-database-projects/sql-database-projects.md
18+
- Keep T/SQL scripts idempotent and version-controlled.
19+
- For local deployment, use `sqlcmd create mssql` to create a local SQL Server instance.
20+
- use the helper script `create-mssql-instance-using-sqlcmd.ps1` in the `./.github/tsql/install` folder
21+
- To get the connection string, run `sqlcmd config connection-strings` (use the helper script `get-sql-connection-string.ps1` in the `./.github/tsql/install` folder).
22+
23+
## Considerations
24+
25+
- Folder structure maps directly to SQL object types.
26+
- Idempotency: scripts must handle repeated executions.
27+
- Naming conventions align with best practices.
28+
- After each step, you **MUST**:
29+
- build (using `Build.ps1`, which takes `-ProjectName` as the single parameter)
30+
- `Build.ps1` is in the `./.github/tsql/inner-loop` folder.
31+
- publish (using `Publish.ps1`, which takes `-ProjectName` as the single parameter)
32+
- `Publish.ps1` is in the `./.github/tsql/inner-loop` folder.
33+
- `Publish.ps1` uses `SqlPackage` to publish the project to the SQL Server.
34+
35+
NOTES:
36+
- **NEVER** output VSCode.Cell elements in the chat window. This will cause the chat to break, instead **Always** put output into files in the workspace (as instructed).
37+
- **DO NOT** ask me if I want to do something OR something else. Until you've completed the last step in this chain of thought, you must not ask me if I want to do something else. Just do each step as instructed.
38+
39+
## Project Structure
40+
41+
---
42+
```text
43+
MyDatabaseProject/ -- root of .sqlproj
44+
MyDatabaseProject.sqlproj -- SQL Database Project file
45+
Properties/ -- Database settings and publish profiles
46+
Tables/ -- CREATE TABLE scripts
47+
Views/ -- CREATE VIEW scripts
48+
Programmability/ -- Functions, Stored Procedures
49+
Functions/
50+
Stored Procedures/
51+
PreDeploy/ -- Pre-deployment scripts
52+
PostDeploy/ -- Post-deployment scripts and data migrations
53+
Tests/ -- tSQLt or other test scripts
54+
```
55+
---
56+
57+
NOTES:
58+
1. **DO NOT*** put any spaces in the folder names, or in the file names. Use PascalCase for folder names and file names.
59+
2. **DO NOT** create `.keep` files, they are not needed.
60+
3. You **MUST NOT** put square brackets, `[` and `]`, in file names or folder names.
61+
4. You **MUST NOT** ever add GO statements to any SQL scripts you generate.
62+
5. All Primary Key ID columns must use IDENTITY(1,1)
63+
6. Table IDs must never be used as Stored Procedures parameters
64+
7. **NEVER** use table valued parameters (TVPs) as Stored Procedure parameters (that will be used as MCP Tools), this is because Data Api Builder does not support table valued parameters in entities.
65+
66+
### Step 0: Thoroughly review the business-process-description.md file
67+
68+
**Thought:** Understand the business process and the entities involved, and look for inconsistencies between the Roles, Entities, Entity Descriptions, Operations and User Stories.
69+
- Look for any missing entities, entity descriptions, or missing operations in the business-process-description.md file.
70+
- Look for any missing User Stories in the business-process-description.md file.
71+
72+
**Action:**
73+
- In the agent-mode chat window, strongly suggest any missing entities, entity descriptions, or operations or user stories to the user. Give very clear reasons why you feel something is missing, especially if you think there is an inconsistency between the Roles, Entities, Entity Descriptions, Operations and User Stories.
74+
- Do not edit the business-process-description.md file directly, just suggest changes in the chat window.
75+
- Particularly look for missing user stories, especially negative cases, i.e. user stories that test unintended behavior by the roles.
76+
77+
### Step 1: Ensure all dependencies are installed
78+
79+
**Thought:** Ensure all dependencies are installed for the SQL Database Project. Make sure you run these actions one at a time (don't try to concatenate them with ; or &).
80+
81+
**Action:**
82+
- Ensure a container runtime (e.g. docker or podman) is running by running `./.github/tsql/install/is-container-runtime-running.ps1`
83+
- If a container runtime is not running, ensure the user does not proceed until a container runtime is installed and running.
84+
- Ensure `SqlPackage` is installed by running `./.github/tsql/install/ensure-sqlpackage-installed.ps1`
85+
- NOTE: If the following message is returned after installing `SqlPackage`: `Since you just installed the .NET SDK, you will need
86+
to reopen the Command Prompt window before running the tool you installed.`, run `exit` in the
87+
terminal window, so another terminal window will be created for the next command.
88+
- NOTE: Running `exit` in the chat window will cause the Agent to hang. The user will need to clik the `Stop` button in the
89+
chat window to stop the agent and type `proceed` to continue.
90+
- Ensure `Build.Sql.Templates` is installed by running `./.github/tsql/install/ensure-build-sql-templates-installed.ps1`
91+
- Ensure `sqlcmd` is installed by running `./.github/tsql/install/ensure-sqlcmd-installed.ps1`
92+
93+
### Step 2: Create a Local SQL Server Instance
94+
95+
**Thought:** Use sqlcmd to create a local SQL Server instance for development.
96+
97+
**Action:**
98+
- Create a local SQL Server instance using the script `create-mssql-instance-using-sqlcmd.ps1` in the `./.github/tsql/install` folder. Pass in the name of the project name as the `-ProjectName` parameter. (this will become the `sqlcmd` `context` name)
99+
- Ensure `tSQLt` is 'prepared' in SQL Server instance by running `./.github/tsql/install/ensure-tsqlt-installed.ps1`
100+
101+
### Step 3: Initialize SQL Database Project
102+
103+
**Thought:** Create a new project to track schema in source control.
104+
105+
**Action:**
106+
- Create a new SQL project using the sqlproj template e.g.: `dotnet new sqlproj -n MyDatabaseProject`
107+
- Make sure the .sqlproj is set to work with SQL Server 2019 (which is Sql150), i.e.: make sure this is in the .sqlproj file:
108+
- `<DSP>Microsoft.Data.Tools.Schema.Sql.Sql150DatabaseSchemaProvider</DSP>`
109+
- This is because the .dacpac for tSQLt is not available for SQL Server 2022 (Sql160) yet.
110+
- Create a .gitignore file in the .sqlproj folder to exclude build artifacts and local settings:
111+
112+
NOTE: **Do not** create a solution file, these are not needed for SQL Database Projects.
113+
114+
### Step 4: Scaffold Tables
115+
116+
**Thought:** Each entity table resides in its own file under `Tables/`.
117+
118+
NOTE:
119+
- Do not include individual files you create in the .sqlproj.
120+
- This is becasue the .NET SDK includes 'Build' items from your project directory by default. (otherwise you will get `error NETSDK1022` when you try to build the project due to Duplicate items)
121+
122+
**Action:**
123+
- e.g. Create `Tables/Customer.sql`:
124+
125+
---
126+
```sql
127+
CREATE TABLE dbo.Customer (
128+
CustomerId INT NOT NULL PRIMARY KEY,
129+
Name NVARCHAR(200) NOT NULL,
130+
IsActive BIT NOT NULL DEFAULT(1)
131+
);
132+
```
133+
---
134+
135+
- Repeat for each business entity.
136+
137+
### Step 5: Scaffold Views and Indexes
138+
139+
**Thought:** Views and indexes improve performance and reuse.
140+
141+
**Action:**
142+
- `Views/ActiveCustomers.sql`: `CREATE VIEW dbo.ActiveCustomers AS SELECT * FROM dbo.Customer WHERE IsActive = 1;`
143+
- `Tables/Customer.indexes.sql`: `CREATE INDEX IX_Customer_Name ON dbo.Customer(Name);`
144+
145+
### Step 6: Scaffold Programmability
146+
147+
**Thought:** Group functions and procedures under `Programmability/`.
148+
149+
**Action:**
150+
- Create a stored procedure for every operation in the business process description.
151+
- Internal IDENTITY value must not be exposed as a parameter.
152+
- Since operations will only be as tools, all parameters will be `NVARCHAR(MAX)`, and all parameters must take text values, and IDENTITY values must be looked up internally to the stored procedure.
153+
- Use `SET NOCOUNT ON`, parameter validation, `TRY...CATCH`, transactions.
154+
155+
### Step 7: Handle Migrations
156+
157+
**Thought:** Use PreDeploy/PostDeploy for data migrations and seed data.
158+
159+
**Action:**
160+
- Add `PreDeploy/DropObsoleteObjects.sql` with `IF EXISTS DROP` patterns.
161+
- Add `PostDeploy/SeedData.sql` with `MERGE` or `IF NOT EXISTS` inserts.
162+
163+
NOTE: You must follow the documentation here to configure the .sqlproj correctly: https://raw.githubusercontent.com/MicrosoftDocs/sql-docs/refs/heads/live/docs/tools/sql-database-projects/concepts/pre-post-deployment-scripts.md, i.e. ensure a PreDeploy and PostDeploy element is included in the .sqlproj
164+
165+
NOTE: You **must** set the BuildAction to None in the .sqlproj for the PreDeploy and PostDeploy files (otherwise error SQL71006 will happen at deployment time). See this StackOverFlow post for details:
166+
- https://stackoverflow.com/questions/18698481/error-in-sql-script-only-one-statement-is-allowed-per-batch
167+
168+
So the .sqlproj should have two additional ItemGroups that look like this:
169+
170+
---
171+
```
172+
<ItemGroup>
173+
<PreDeploy Include="PreDeploy\DropObsoleteObjects.sql" />
174+
<PostDeploy Include="PostDeploy\SeedData.sql" / >
175+
</ItemGroup>
176+
```
177+
---
178+
179+
and
180+
181+
---
182+
```
183+
<ItemGroup>
184+
<Build Remove="PreDeploy\DropObsoleteObjects.sql" />
185+
<Build Remove="PostDeploy\SeedData.sql" />
186+
</ItemGroup>
187+
```
188+
---
189+
190+
### Step 8: Build and Publish to SQL Server
191+
192+
**Thought:** Run a build of the .sqlproj, and then publish the project to the SQL Server using SqlPackage.
193+
194+
**Action:**
195+
- Build by running `Build.ps1` (which takes `-ProjectName` as the single parameter) and ensure no errors.
196+
- Publish to the SQL Server using SqlPackage by running `Publish.ps1` (which takes `-ProjectName` as the single parameter) and ensure no errors.
197+
198+
### Step 9: Setup for adding tSQLt tests to the project
199+
200+
**Action:**
201+
- Create test .sql files in the `./Tests` folder.
202+
- Create a .sql file that creates the [UserStoryTests] schema with dbo authorization.
203+
- Add another .sql file that creates the 'tSQLt.TestClass' extended property on the 'UserStoryTests' SCHEMA object. This enables tSQLt for the [UserStoryTests] schema i.e.:
204+
`EXECUTE sp_addextendedproperty @name = N'tSQLt.TestClass', @value = 1, @level0type = N'SCHEMA', @level0name = N'UserStoryTests';`
205+
- Copy the tSQLt.2019.dacpac file to the `./Tests` folder using the `copy-tSQLt.dacpac-file.ps1` script. (the script takes -ProjectPath parameter)
206+
- Add a reference in the .sqlproj file to the tSQLt.2019.dacpac file in the `./Tests` folder. This is done by adding a new ItemGroup to the .sqlproj file that looks like this:
207+
208+
---
209+
```
210+
<ItemGroup>
211+
<ArtifactReference Include="Tests\tSQLt.2019.dacpac">
212+
<SuppressMissingDependenciesErrors>False</SuppressMissingDependenciesErrors>
213+
</ArtifactReference>
214+
</ItemGroup>
215+
```
216+
---
217+
218+
### Step 10: Build and Publish tSQLt setup to SQL Server
219+
220+
**Action:**
221+
- Build by running `Build.ps1` (which takes `-ProjectName` as the single parameter) and ensure no errors.
222+
- Publish to the SQL Server using SqlPackage by running `Publish.ps1` (which takes `-ProjectName` as the single parameter) and ensure no errors.
223+
224+
### Step 11: Create a tSQLt test class for each user story
225+
226+
**Thought:** For each User Story in the Business Process Description create a .sql file with a single stored procedure that tests the user story.
227+
- The test class should be named after the user story and **MUST** be in the [UserStoryTests] schema.
228+
- All stored procedures names **MUST** start with the word `test`.
229+
- tSQLt requires all stored procedures to start with the word `test`.
230+
- All tSQLt parameter names are Pascal Case, and **MUST** start with an UPPER case letter.
231+
- i.e. to avoid this build warning `Build warning SQL71558: The object reference [tSQLt].[AssertEquals].[@expected] differs only by case from the object definition [tSQLt].[AssertEquals].[@Expected]. `. Make sure you use Pascal Case parameter names for tSQLt programmability objects like `@Expected` (and **NOT** `@expected`).
232+
233+
**Action:**
234+
- Create a .sql file in the Tests folder for each User Story.
235+
- Make sure each user story is in one .sql file.
236+
- You **MUST NOT** use GO separators in the test .sql files.
237+
- You **MUST** only put one CREATE STORED PROCEDURE statement in each test .sql file.
238+
- You **MUST** put the stored procedure in the [UserStoryTests] schema.
239+
- You **MUST** start all stored procedure names with the word `test`.
240+
- You **MUST NOT** put a CREATE SCHEMA statement in the test .sql files. (this is because tSQLt already created the [UserStoryTests] schema for you)
241+
242+
### Step 12: Ensure all User Stories are fully implemented as tSQLt tests.
243+
244+
**Thought:** The above step can create .sql files in the `./Tests` folder, that have NotYetImplemented in them.
245+
246+
**Action:**
247+
- Ensure each User Story is fully implemented as a tSQLt test in a .sql file.
248+
- Exhaustively check each line of each user story is full validated by a test.
249+
250+
### Step 13: Check .sql files in ./Tests folder have all been saved
251+
252+
**Thought:** The above step can take a long time to complete, and Agent-Mode runs
253+
in parallel and asynchronously, so user needs to check that all the .sql files
254+
in the ./Tests folder have been saved before proceeding to the next step.
255+
256+
**Action:**
257+
- The user must check that all the test .sql files have been saved in the `./Tests` folder.
258+
- If not, user must wait to proceed until all .sql files in the ./Tests folder have been saved by the agent.
259+
260+
### Step 14: Build and Publish the tSQLt User Story tests to SQL Server
261+
262+
**Action:**
263+
- Build by running `Build.ps1` (which takes `-ProjectName` as the single parameter) and ensure no errors.
264+
- Publish to the SQL Server using SqlPackage by running `Publish.ps1` (which takes `-ProjectName` as the single parameter) and ensure no errors.
265+
266+
### Step 15: Run user story tests until they all pass.
267+
268+
**Action:**
269+
- Run `Test.ps1` (which takes `-ProjectName` as the single parameter) to run all the tests in the [UserStoryTests] schema. `Test.ps1` is in the `./.github/tsql/inner-loop` folder.
270+
- Ensure all tSQLt tests are passing before proceeding to deploy to Azure.
271+
272+
### THE END
273+
274+
### Conclusion
275+
276+
By following this chain-of-thought guide, AI assistants can scaffold a robust SQL Database Project with clear separation of concerns, versioning and a well defined Build, Publish, Test inner-loop for local development.
277+
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# Copyright (c) Microsoft Corporation. All rights reserved.
2+
# Licensed under the MIT license.
3+
4+
param (
5+
[string]$ProjectName
6+
)
7+
8+
# Run dotnet build
9+
dotnet build "$ProjectName/$ProjectName.sqlproj"
10+
if ($LASTEXITCODE -ne 0) {
11+
Write-Host "Failed to build the project. Please check the output for errors."
12+
exit 1
13+
}
14+
15+
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# Copyright (c) Microsoft Corporation. All rights reserved.
2+
# Licensed under the MIT license.
3+
4+
# -SourceFile e.g. ConfMS/bin/Debug/ConfMS.dacpac
5+
param (
6+
[string]$ProjectName
7+
)
8+
9+
# Get the target connection string by running `get-sql-connection-string.ps1`
10+
$targetConnectionString = & ./.github/tsql/install/get-sql-connection-string.ps1
11+
12+
# Ensure the connection string is wrapped in double quotes for SqlPackage.exe
13+
$quotedConnectionString = '"' + $targetConnectionString + '"'
14+
15+
# Define the source file path
16+
$sourceFile = "./$ProjectName/bin/Debug/$ProjectName.dacpac"
17+
if (!(Test-Path $sourceFile)) {
18+
$sourceFile = "../$ProjectName/bin/Debug/$ProjectName.dacpac"
19+
}
20+
21+
# Print the source file path
22+
Write-Host "Source file: $sourceFile"
23+
24+
# Mask password in connection string for display
25+
$maskedConnectionString = $targetConnectionString -replace '(?i)(Password|Pwd)=([^;]*)', '$1=*****'
26+
27+
# Print the publish command with masked password
28+
$maskedQuotedConnectionString = '"' + $maskedConnectionString + '"'
29+
$maskedPublishCommand = "$env:USERPROFILE\.dotnet\tools\SqlPackage.exe /Action:Publish /SourceFile:$sourceFile /TargetConnectionString:$maskedQuotedConnectionString /p:IncludeCompositeObjects=true"
30+
$maskedPublishCommand = $maskedPublishCommand -replace '\$', '`$' # Escape $ in the connection string
31+
Write-Host "Running command: $maskedPublishCommand"
32+
33+
# Run the actual publish command (with real password)
34+
$publishCommand = "$env:USERPROFILE\.dotnet\tools\SqlPackage.exe /Action:Publish /SourceFile:$sourceFile /TargetConnectionString:$quotedConnectionString /p:IncludeCompositeObjects=true"
35+
$publishCommand = $publishCommand -replace '\$', '`$' # Escape $ in the connection string
36+
Invoke-Expression $publishCommand
37+
if ($LASTEXITCODE -ne 0) {
38+
Write-Host "Failed to publish the DACPAC. Please check the output for errors."
39+
exit 1
40+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Copyright (c) Microsoft Corporation. All rights reserved.
2+
# Licensed under the MIT license.
3+
4+
# Run `sqlcmd query "EXEC tSQLt.RunAll`
5+
& $env:ProgramFiles\SqlCmd\sqlcmd.exe query "EXEC tSQLt.RunAll"
6+
7+
8+
9+
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# Copyright (c) Microsoft Corporation. All rights reserved.
2+
# Licensed under the MIT license.
3+
4+
# This powershell script takes a $projectPath
5+
# as a parameter and copies the tSQLt.dacpac file to the $projectPath\Tests
6+
# from the "$env:TEMP\tSQLt\tSQLtDacpacs" folder
7+
8+
param (
9+
[string]$projectPath
10+
)
11+
12+
# Define the temp folder for unzipped files
13+
$tempUnzipFolder = "$env:TEMP\tSQLt\tSQLtDacpacs"
14+
# Define the tSQLt class file name
15+
$tsqltDacPacFile = "tSQLt.2019.dacpac"
16+
# Define the tSQLt class file path
17+
$tsqltDacPacPath = "$tempUnzipFolder\$tsqltDacPacFile"
18+
# Check if the tSQLt class file exists
19+
if (Test-Path $tsqltDacPacPath) {
20+
# Copy the tSQLt.dacpac to the destination folder
21+
Copy-Item -Path $tsqltDacPacPath -Destination $projectPath\Tests -Force
22+
Write-Host "$tsqltDacPacFile file copied to $projectPath\Tests"
23+
} else {
24+
Write-Error "$tsqltDacPacFile file not found in $tempUnzipFolder"
25+
}
26+
27+
28+

0 commit comments

Comments
 (0)