|
| 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 | + |
0 commit comments