Skip to content

Commit 67b247b

Browse files
committed
VSCode Agent Mode sample for T/SQL applications
1 parent 50f0609 commit 67b247b

15 files changed

+887
-0
lines changed
Lines changed: 264 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,264 @@
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+
82+
**Action:**
83+
- Ensure a container runtime (e.g. docker or podman) is running by running `./.github/tsql/install/is-container-runtime-running.ps1`
84+
- Ensure `sqlcmd` is installed by running `./.github/tsql/install/ensure-sqlcmd-installed.ps1`
85+
- Ensure `Build.Sql.Templates` is installed by running `./.github/tsql/install/ensure-build-sql-templates-installed.ps1`
86+
- Ensure `SqlPackage` is installed by running `./.github/tsql/install/ensure-sqlpackage-installed.ps1`
87+
88+
### Step 2: Create a Local SQL Server Instance
89+
90+
**Thought:** Use sqlcmd to create a local SQL Server instance for development.
91+
92+
**Action:**
93+
- 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)
94+
- Ensure `tSQLt` is 'prepared' in SQL Server instance by running `./.github/tsql/install/ensure-tsqlt-installed.ps1`
95+
96+
### Step 3: Initialize SQL Database Project
97+
98+
**Thought:** Create a new project to track schema in source control.
99+
100+
**Action:**
101+
- Create a new SQL project using the sqlproj template e.g.: `dotnet new sqlproj -n MyDatabaseProject`
102+
- 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:
103+
- `<DSP>Microsoft.Data.Tools.Schema.Sql.Sql150DatabaseSchemaProvider</DSP>`
104+
- This is because the .dacpac for tSQLt is not available for SQL Server 2022 (Sql160) yet.
105+
- Create a .gitignore file in the .sqlproj folder to exclude build artifacts and local settings:
106+
107+
NOTE: **Do not** create a solution file, these are not needed for SQL Database Projects.
108+
109+
### Step 4: Scaffold Tables
110+
111+
**Thought:** Each entity table resides in its own file under `Tables/`.
112+
113+
NOTE:
114+
- Do not include individual files you create in the .sqlproj.
115+
- 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)
116+
117+
**Action:**
118+
- e.g. Create `Tables/Customer.sql`:
119+
120+
---
121+
```sql
122+
CREATE TABLE dbo.Customer (
123+
CustomerId INT NOT NULL PRIMARY KEY,
124+
Name NVARCHAR(200) NOT NULL,
125+
IsActive BIT NOT NULL DEFAULT(1)
126+
);
127+
```
128+
---
129+
130+
- Repeat for each business entity.
131+
132+
### Step 5: Scaffold Views and Indexes
133+
134+
**Thought:** Views and indexes improve performance and reuse.
135+
136+
**Action:**
137+
- `Views/ActiveCustomers.sql`: `CREATE VIEW dbo.ActiveCustomers AS SELECT * FROM dbo.Customer WHERE IsActive = 1;`
138+
- `Tables/Customer.indexes.sql`: `CREATE INDEX IX_Customer_Name ON dbo.Customer(Name);`
139+
140+
### Step 6: Scaffold Programmability
141+
142+
**Thought:** Group functions and procedures under `Programmability/`.
143+
144+
**Action:**
145+
- Create a stored procedure for every operation in the business process description.
146+
- Internal IDENTITY value must not be exposed as a parameter.
147+
- 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.
148+
- Use `SET NOCOUNT ON`, parameter validation, `TRY...CATCH`, transactions.
149+
150+
### Step 7: Handle Migrations
151+
152+
**Thought:** Use PreDeploy/PostDeploy for data migrations and seed data.
153+
154+
**Action:**
155+
- Add `PreDeploy/DropObsoleteObjects.sql` with `IF EXISTS DROP` patterns.
156+
- Add `PostDeploy/SeedData.sql` with `MERGE` or `IF NOT EXISTS` inserts.
157+
158+
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
159+
160+
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:
161+
- https://stackoverflow.com/questions/18698481/error-in-sql-script-only-one-statement-is-allowed-per-batch
162+
163+
So the .sqlproj should have two additional ItemGroups that look like this:
164+
165+
---
166+
```
167+
<ItemGroup>
168+
<PreDeploy Include="PreDeploy\DropObsoleteObjects.sql" />
169+
<PostDeploy Include="PostDeploy\SeedData.sql" / >
170+
</ItemGroup>
171+
```
172+
---
173+
174+
and
175+
176+
---
177+
```
178+
<ItemGroup>
179+
<Build Remove="PreDeploy\DropObsoleteObjects.sql" />
180+
<Build Remove="PostDeploy\SeedData.sql" />
181+
</ItemGroup>
182+
```
183+
---
184+
185+
### Step 8: Build and Publish to SQL Server
186+
187+
**Thought:** Run a build of the .sqlproj, and then publish the project to the SQL Server using SqlPackage.
188+
189+
**Action:**
190+
- Build by running `Build.ps1` (which takes `-ProjectName` as the single parameter) and ensure no errors.
191+
- Publish to the SQL Server using SqlPackage by running `Publish.ps1` (which takes `-ProjectName` as the single parameter) and ensure no errors.
192+
193+
### Step 9: Setup for adding tSQLt tests to the project
194+
195+
**Action:**
196+
- Create test .sql files in the `./Tests` folder.
197+
- Create a .sql file that creates the [UserStoryTests] schema with dbo authorization.
198+
- 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.:
199+
`EXECUTE sp_addextendedproperty @name = N'tSQLt.TestClass', @value = 1, @level0type = N'SCHEMA', @level0name = N'UserStoryTests';`
200+
- Copy the tSQLt.2019.dacpac file to the `./Tests` folder using the `copy-tSQLt.dacpac-file.ps1` script. (the script takes -ProjectPath parameter)
201+
- 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:
202+
203+
---
204+
```
205+
<ItemGroup>
206+
<ArtifactReference Include="Tests\tSQLt.2019.dacpac">
207+
<SuppressMissingDependenciesErrors>False</SuppressMissingDependenciesErrors>
208+
</ArtifactReference>
209+
</ItemGroup>
210+
```
211+
---
212+
213+
### Step 10: Build and Publish tSQLt setup to SQL Server
214+
215+
**Action:**
216+
- Build by running `Build.ps1` (which takes `-ProjectName` as the single parameter) and ensure no errors.
217+
- Publish to the SQL Server using SqlPackage by running `Publish.ps1` (which takes `-ProjectName` as the single parameter) and ensure no errors.
218+
219+
### Step 11: Create a tSQLt test class for each user story
220+
221+
**Thought:** For each User Story in the Business Process Description create a .sql file with a single stored procedure that tests the user story.
222+
- The test class should be named after the user story and **MUST** be in the [UserStoryTests] schema.
223+
- All stored procedures names **MUST** start with the word `test`.
224+
- tSQLt requires all stored procedures to start with the word `test`.
225+
- All tSQLt parameter names are Pascal Case, and **MUST** start with an UPPER case letter.
226+
- 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`).
227+
228+
**Action:**
229+
- Create a .sql file in the Tests folder for each User Story.
230+
- Make sure each user story is in one .sql file.
231+
- You **MUST NOT** use GO separators in the test .sql files.
232+
- You **MUST** only put one CREATE STORED PROCEDURE statement in each test .sql file.
233+
- You **MUST** put the stored procedure in the [UserStoryTests] schema.
234+
- You **MUST** start all stored procedure names with the word `test`.
235+
- You **MUST NOT** put a CREATE SCHEMA statement in the test .sql files. (this is because tSQLt already created the [UserStoryTests] schema for you)
236+
237+
### Step 12: User to check .sql files in ./Tests folder have all been saved
238+
239+
**Thought:** The above step can take a long time to complete, and Agent-Mode runs
240+
in parallel and asynchronously, so user needs to check that all the .sql files
241+
in the ./Tests folder have been saved before proceeding to the next step.
242+
243+
**Action:**
244+
- The user must check that all the test .sql files have been saved in the `./Tests` folder.
245+
- If not, user must wait to proceed until all .sql files in the ./Tests folder have been saved by the agent.
246+
247+
### Step 13: Build and Publish the tSQLt User Story tests to SQL Server
248+
249+
**Action:**
250+
- Build by running `Build.ps1` (which takes `-ProjectName` as the single parameter) and ensure no errors.
251+
- Publish to the SQL Server using SqlPackage by running `Publish.ps1` (which takes `-ProjectName` as the single parameter) and ensure no errors.
252+
253+
### Step 14: Run user story tests until they all pass.
254+
255+
**Action:**
256+
- 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.
257+
- Ensure all tSQLt tests are passing before proceeding to deploy to Azure.
258+
259+
### THE END
260+
261+
### Conclusion
262+
263+
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.
264+
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
param (
2+
[string]$ProjectName
3+
)
4+
5+
# Run dotnet build
6+
dotnet build "$ProjectName/$ProjectName.sqlproj"
7+
if ($LASTEXITCODE -ne 0) {
8+
Write-Host "Failed to build the project. Please check the output for errors."
9+
exit 1
10+
}
11+
12+
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# -SourceFile e.g. ConfMS/bin/Debug/ConfMS.dacpac
2+
param (
3+
[string]$ProjectName
4+
)
5+
6+
# Get the target connection string by running `get-sql-connection-string.ps1`
7+
$targetConnectionString = & ./.github/tsql/install/get-sql-connection-string.ps1
8+
9+
# Ensure the connection string is wrapped in double quotes for SqlPackage.exe
10+
$quotedConnectionString = '"' + $targetConnectionString + '"'
11+
12+
# Define the source file path
13+
$sourceFile = "./$ProjectName/bin/Debug/$ProjectName.dacpac"
14+
if (!(Test-Path $sourceFile)) {
15+
$sourceFile = "../$ProjectName/bin/Debug/$ProjectName.dacpac"
16+
}
17+
18+
# Print the source file path
19+
Write-Host "Source file: $sourceFile"
20+
# Run SqlPackage.exe /Action:Publish /SourceFile:$SourceFile /TargetConnectionString:$targetConnectionString /p:IncludeCompositeObjects=true
21+
# Note $targetConnectionString can have $ in it
22+
$publishCommand = "SqlPackage.exe /Action:Publish /SourceFile:$sourceFile /TargetConnectionString:$quotedConnectionString /p:IncludeCompositeObjects=true"
23+
$publishCommand = $publishCommand -replace '\$', '`$' # Escape $ in the connection string
24+
Write-Host "Running command: $publishCommand"
25+
Invoke-Expression $publishCommand
26+
if ($LASTEXITCODE -ne 0) {
27+
Write-Host "Failed to publish the DACPAC. Please check the output for errors."
28+
exit 1
29+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# Run `sqlcmd query "EXEC tSQLt.RunAll`
2+
3+
& $env:ProgramFiles\SqlCmd\sqlcmd.exe query "EXEC tSQLt.RunAll"
4+
5+
6+
7+
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# This powershell script takes a $projectPath
2+
# as a parameter and copies the tSQLt.dacpac file to the $projectPath\Tests
3+
# from the "$env:TEMP\tSQLt\tSQLtDacpacs" folder
4+
5+
param (
6+
[string]$projectPath
7+
)
8+
9+
# Define the temp folder for unzipped files
10+
$tempUnzipFolder = "$env:TEMP\tSQLt\tSQLtDacpacs"
11+
# Define the tSQLt class file name
12+
$tsqltDacPacFile = "tSQLt.2019.dacpac"
13+
# Define the tSQLt class file path
14+
$tsqltDacPacPath = "$tempUnzipFolder\$tsqltDacPacFile"
15+
# Check if the tSQLt class file exists
16+
if (Test-Path $tsqltDacPacPath) {
17+
# Copy the tSQLt.dacpac to the destination folder
18+
Copy-Item -Path $tsqltDacPacPath -Destination $projectPath\Tests -Force
19+
Write-Host "$tsqltDacPacFile file copied to $projectPath\Tests"
20+
} else {
21+
Write-Error "$tsqltDacPacFile file not found in $tempUnzipFolder"
22+
}
23+
24+
25+
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# This script takes a parameter which is the context-name (passed into sqlcmd)
2+
# and creates a new SQL Server instance using the sqlcmd command line tool:
3+
# `sqlcmd create mssql --tag 2019-latest --context-name $ContextName`
4+
5+
# If the context already exists, it will be removed first.
6+
7+
param (
8+
[string]$ProjectName
9+
)
10+
11+
# Run `sqlcmd config get-contexts`, it returns a lists of contexts, e.g.
12+
#
13+
# ```
14+
# - mssql
15+
# - mssql2
16+
# - mssql3
17+
# ```
18+
19+
# Check if the context already exists
20+
$existingContexts = & $env:ProgramFiles\SqlCmd\sqlcmd.exe config get-contexts
21+
22+
# Normalize context names by trimming and removing dashes
23+
$normalizedContexts = $existingContexts | ForEach-Object { $_.Trim().TrimStart('-').Trim() }
24+
$contextExists = $normalizedContexts | Where-Object { $_ -ieq $ProjectName }
25+
if ($contextExists) {
26+
# Run `sqlcmd use-context $ProjectName`, it sets the current context, which we'll then delete
27+
& $env:ProgramFiles\SqlCmd\sqlcmd.exe config use-context $ProjectName
28+
29+
# Delete the context by running `sqlcmd delete --force --yes`
30+
& $env:ProgramFiles\SqlCmd\sqlcmd.exe delete --force --yes
31+
}
32+
33+
# Create a new SQL Server instance using the sqlcmd command line tool
34+
& $env:ProgramFiles\SqlCmd\sqlcmd.exe create mssql --tag 2019-latest --accept-eula --user-database $ProjectName --context-name $ProjectName
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Use the dotnet CLI to see if Build.Sql.Templates are installed, if not, install them
2+
3+
# Check if Build.Sql.Templates is installed
4+
$installed = dotnet new list | Select-String -Pattern "SQL Server Database Project"
5+
if ($installed -eq $null) {
6+
Write-Host "Build.Sql.Templates is not installed. Installing..."
7+
# Install Build.Sql.Templates
8+
dotnet new install Microsoft.Build.Sql.Templates
9+
} else {
10+
Write-Host "Build.Sql.Templates is already installed."
11+
}

0 commit comments

Comments
 (0)