From 9cd09907076f66708ed3ef7acc6db970cef6af00 Mon Sep 17 00:00:00 2001 From: mkincaid-bw Date: Wed, 13 Aug 2025 15:13:44 -0700 Subject: [PATCH 1/8] Updated SQL code style to mostly match our current codebase --- docs/contributing/code-style/sql.md | 302 +++++++++++++++++++++++++++- 1 file changed, 298 insertions(+), 4 deletions(-) diff --git a/docs/contributing/code-style/sql.md b/docs/contributing/code-style/sql.md index d220662c5..880fce07f 100644 --- a/docs/contributing/code-style/sql.md +++ b/docs/contributing/code-style/sql.md @@ -4,13 +4,35 @@ toc_max_heading_level: 4 # T-SQL -## Repositories +## Overview We use the [Repository pattern][repository] with the MSSQL repositories being written using [Dapper][dapper]. Each repository method in turn calls a _Stored Procedure_, which primarily fetches data from _Views_. -## Deployment scripts +## File Organization + +### Directory Structure +- **Schema-based organization**: Files are organized by domain/schema (Auth, Billing, SecretsManager, Vault, etc.) +- **Object type grouping**: Within each domain, files are grouped by type: + - `Tables/` - Table definitions + - `Views/` - View definitions + - `Stored Procedures/` - Stored procedure definitions + - `Functions/` - User-defined functions +- **Root-level objects**: Common objects are placed directly in `dbo/`: + - `Stored Procedures/` - General stored procedures + - `Tables/` - Core tables + - `Views/` - General views + - `User Defined Types/` - Custom data types + +### File Naming Conventions +- **Stored Procedures**: `{EntityName}_{Action}.sql` (e.g., `User_Create.sql`, `Organization_ReadById.sql`) +- **Tables**: `{EntityName}.sql` (e.g., `User.sql`, `Organization.sql`) +- **Views**: `{EntityName}View.sql` or `{EntityName}{Purpose}View.sql` (e.g., `UserView.sql`, `OrganizationUserUserDetailsView.sql`) +- **Functions**: `{EntityName}{Purpose}.sql` (e.g., `UserCollectionDetails.sql`) +- **User Defined Types**: `{TypeName}.sql` (e.g., `GuidIdArray.sql`) + +## Deployment Scripts There are specific ways deployment scripts should be structured. The goal for these standards is to ensure that the scripts should be re-runnable. We never intend to run scripts multiple times on an @@ -18,6 +40,15 @@ environment, but the scripts should support it. ### Tables +#### Naming Conventions +- **Table Names**: PascalCase (e.g., `[dbo].[User]`, `[dbo].[AuthRequest]`) +- **Column Names**: PascalCase (e.g., `[Id]`, `[CreationDate]`, `[MasterPasswordHash]`) +- **Primary Keys**: `PK_{TableName}` (e.g., `[PK_User]`, `[PK_Organization]`) +- **Foreign Keys**: `FK_{TableName}_{ReferencedTable}` or `FK_{TableName}_{ColumnName}` (e.g., `[FK_AuthRequest_User]`) +- *PROPOSED: **Foreign Keys**: `FK_{TableName}_{ColumnName}__{ReferencedTable}_{ReferencedColumn}` (e.g., `FK_Device_UserId__User_Id`) + - **Note**: Constraint name limits: SQL Server (128 chars), PostgreSQL (63 chars), MySQL (64 chars), SQLite (unlimited) +- **Default Constraints**: `DF_{TableName}_{ColumnName}` (e.g., `[DF_Organization_UseScim]`) + #### Creating a table When creating a table, you must first check if the table exists: @@ -27,13 +58,32 @@ IF OBJECT_ID('[dbo].[{table_name}]') IS NULL BEGIN CREATE TABLE [dbo].[{table_name}] ( [Id] UNIQUEIDENTIFIER NOT NULL, - ... - CONSTRAINT [PK_{table_name}] PRIMARY KEY CLUSTERED ([Id] ASC) + [Column1] DATATYPE NOT NULL, + [Column2] DATATYPE NULL, + [CreationDate] DATETIME2(7) NOT NULL, + [RevisionDate] DATETIME2(7) NOT NULL, + CONSTRAINT [PK_{table_name}] PRIMARY KEY CLUSTERED ([Id] ASC), + CONSTRAINT [FK_{table_name}_{referenced_table}] FOREIGN KEY ([ForeignKeyColumn]) REFERENCES [dbo].[ReferencedTable] ([Id]) ); END GO ``` +##### Column Definition Standards +- **Alignment**: Column names, data types, and nullability vertically aligned using spaces +- **Data Types**: Use consistent type patterns: + - `UNIQUEIDENTIFIER` for IDs + - `DATETIME2(7)` for timestamps + - `NVARCHAR(n)` for Unicode text + - `VARCHAR(n)` for ASCII text + - `BIT` for boolean values + - `TINYINT`, `SMALLINT`, `INT`, `BIGINT` for integers +- **Nullability**: Explicitly specify `NOT NULL` or `NULL` +- **Standard Columns**: Most tables include: + - `[Id] UNIQUEIDENTIFIER NOT NULL` - Primary key + - `[CreationDate] DATETIME2(7) NOT NULL` - Record creation timestamp + - `[RevisionDate] DATETIME2(7) NOT NULL` - Last modification timestamp + #### Deleting a table When deleting a table, use `IF EXISTS` to avoid an error if the table doesn't exist. @@ -140,6 +190,19 @@ GO ### Views +#### Naming Conventions +- **View Names**: + - `{EntityName}View` + - Used when the view maps closely to a single table, with little or no joins. (e.g., (e.g., `[dbo].[ApiKeyView]`) (from ApiKey)) + - `{EntityName}DetailsView` for complex views + - Used for views that combine multiple tables or add logic beyond a basic table select. These usually serve a specific display or reporting use case and are named to reflect the context. (e.g., `[dbo].[OrganizationUserDetailsView]`) + +- For more complex reads that join multiple tables: + - Create a view with a clear name tied to the main entity: + - `[dbo].[OrganizationUser_MemberAccessDetailsView]` + - Create a stored procedure that reads from it: + - `[dbo].[OrganizationUser_ReadMemberAccessDetails]` + #### Creating or modifying a view We recommend using the `CREATE OR ALTER` syntax for adding or modifying a view. @@ -179,6 +242,13 @@ GO ### Functions and stored procedures +#### Naming Conventions +- **Stored Procedures**: `{EntityName}_{Action}` format (e.g., `[dbo].[User_ReadById]`) + - EntityName: The main table or concept (e.g. User, Organization, Cipher) + - Action: What the procedure does (e.g. Create, ReadById, DeleteMany) +- **Parameters**: Start with `@` and use PascalCase (e.g., `@UserId`, `@OrganizationId`) +- **OUTPUT parameters**: Explicitly declare with `OUTPUT` keyword + #### Creating or modifying a function or stored procedure We recommend using the `CREATE OR ALTER` syntax for adding or modifying a function or stored @@ -218,6 +288,230 @@ CREATE NONCLUSTERED INDEX [IX_OrganizationUser_UserIdOrganizationIdStatus] WITH (ONLINE = ON); -- ** THIS ENSURES ONLINE ** ``` +#### Naming Conventions +- **Indexes**: `IX_{TableName}_{ColumnName(s)}` (e.g., `[IX_User_Email]`) + - The name should clearly indicate the table and the columns being indexed. + +#### Index Best Practices +- Create indexes after table definition with `GO` separator +- Use descriptive names following `IX_{TableName}_{ColumnName}` pattern +- Include `INCLUDE` clause when beneficial for covering indexes +- Use filtered indexes with `WHERE` clause when appropriate +- Consider `ONLINE = ON` for production deployments on heavily used tables + +## General Naming Conventions + +### Schema and Object Prefixes +- **Schema**: Use `[dbo]` prefix for all objects +- **Object names**: Always use square brackets `[dbo].[TableName]` + +### User Defined Types +- **User Defined Types**: `{TypeName}.sql` (e.g., `GuidIdArray.sql`) + +## Code Formatting Standards + +### General Formatting + +The adage **`code is written once and read many times`** is one reason that creating easily readable code is very important. Once a developer has acclimated to the conventions and style in this document, they will be able to efficiently read any similarly written code by other developers. + +- **Indentation**: Use 4 spaces (not tabs) for all code files, including SQL +- **Keywords**: Use UPPERCASE for all SQL keywords (`CREATE`, `SELECT`, `FROM`, `WHERE`, `GROUP BY`, `ORDER BY`, `JOIN`, `ON`, `INTO`, `TOP`, etc.) +- **Object names**: Always use square brackets `[dbo].[TableName]` +- **Line endings**: Use consistent line breaks with proper indentation +- **Trailing spaces**: Should be trimmed from the end of lines. Use `[ \t]+$` as a regex find/replace +- **Vertical lists**: Vertically list items as much as feasibly possible, and use consistent indentation to make vertical listing quick and easy. A vertical list is much easier to compare and makes code changes easily detectable +- **Whitespace**: Place one space, unless more are needed for good alignment, after each separating character, such as `+ - * / = & | %` and commas +- **Blank lines**: Separate sections of code with at least one blank line +- **Commas**: Commas should be placed at the right end of the line +- **Join clauses and logical operators**: Should be placed at the right end of the line + +### Stored Procedures + +#### Basic Structure +```sql +CREATE PROCEDURE [dbo].[EntityName_Action] + @Parameter1 DATATYPE, + @Parameter2 DATATYPE = NULL, + @Parameter3 DATATYPE OUTPUT +AS +BEGIN + SET NOCOUNT ON + + -- Procedure logic here + +END +``` + +#### Parameter Declaration +- One parameter per line +- Align parameters with consistent indentation (4 spaces after procedure name) +- Default values on same line as parameter +- OUTPUT parameters clearly marked + +#### SELECT Statements +- `SELECT` keyword on its own line +- Column names indented (4 spaces) +- One column per line for multi-column selects +- `FROM` clause on separate line, aligned with `SELECT` +- `JOIN` clauses indented to align with table names +- `WHERE` clause on separate line, aligned with `FROM` + +```sql +SELECT + U.[Id], + U.[Name], + U.[Email], + OU.[OrganizationId] +FROM + [dbo].[User] U +INNER JOIN + [dbo].[OrganizationUser] OU ON U.[Id] = OU.[UserId] +WHERE + U.[Enabled] = 1 +``` + +#### INSERT Statements +- Column list in parentheses, one column per line +- VALUES clause with parameters aligned +- Proper indentation for readability + +```sql +INSERT INTO [dbo].[TableName] +( + [Column1], + [Column2], + [Column3] +) +VALUES +( + @Parameter1, + @Parameter2, + @Parameter3 +) +``` + +#### UPDATE Statements +- `UPDATE` and table name on same line +- `SET` clause with each column assignment on separate line +- `WHERE` clause clearly separated + +```sql +UPDATE + [dbo].[TableName] +SET + [Column1] = @Parameter1, + [Column2] = @Parameter2, + [Column3] = @Parameter3 +WHERE + [Id] = @Id +``` + +### Views + +#### Simple Views +```sql +CREATE VIEW [dbo].[ViewName] +AS +SELECT + * +FROM + [dbo].[TableName] +``` + +#### Complex Views +```sql +CREATE VIEW [dbo].[ComplexViewName] +AS +SELECT + T1.[Column1], + T1.[Column2], + T2.[Column3], + CASE + WHEN T1.[Status] = 1 + THEN 'Active' + ELSE 'Inactive' + END [StatusText] +FROM + [dbo].[Table1] T1 +LEFT JOIN + [dbo].[Table2] T2 ON T1.[Id] = T2.[ForeignId] +WHERE + T1.[Enabled] = 1 +``` + +### Functions + +#### Naming Conventions +- **Function Names**: `[Schema].[FunctionName]` (e.g., `[dbo].[UserCollectionDetails]`). + - The name should describe what the function returns + +#### Table-Valued Functions +```sql +CREATE FUNCTION [dbo].[FunctionName](@Parameter DATATYPE) +RETURNS TABLE +AS RETURN +SELECT + Column1, + Column2, + CASE + WHEN Condition + THEN Value1 + ELSE Value2 + END [ComputedColumn] +FROM + [dbo].[TableName] +WHERE + [FilterColumn] = @Parameter +``` + +### User Defined Types +- **Naming**: `[Schema].[TypeName]` (e.g., `[dbo].[GuidIdArray]`). + - The name should describe the type. + +```sql +CREATE TYPE [dbo].[TypeName] AS TABLE ( + [Column1] DATATYPE NOT NULL, + [Column2] DATATYPE NOT NULL +); +``` + +## Common Patterns + +### CRUD Operations +- **Create**: `{EntityName}_Create` procedures +- **Read**: `{EntityName}_ReadById`, `{EntityName}_ReadBy{Criteria}` procedures +- **Update**: `{EntityName}_Update` procedures +- **Delete**: `{EntityName}_DeleteById`, `{EntityName}_Delete` procedures + +### Parameter Patterns +- `@Id UNIQUEIDENTIFIER` for entity identifiers +- `@UserId UNIQUEIDENTIFIER` for user references +- `@OrganizationId UNIQUEIDENTIFIER` for organization references +- `@CreationDate DATETIME2(7)` and `@RevisionDate DATETIME2(7)` for timestamps + +### Error Handling +- Use `SET NOCOUNT ON` in stored procedures +- Implement appropriate transaction handling where needed +- Follow consistent error reporting patterns + +## Comments and Documentation + +- Use `--` for single-line comments +- Add comments for complex business logic and the reason for a command or block of code +- Document magic numbers and status codes (e.g., `-- 2 = Confirmed`) +- Provide brief explanations for complex CASE statements or calculations +- Don't comment unnecessarily, such as commenting that an insert statement is about to be executed + +## Best Practices + +1. **Consistency**: Follow established patterns throughout the codebase +2. **Readability**: Prioritize code readability and maintainability +3. **Performance**: Consider index usage and query optimization +4. **Security**: Use parameterized queries and proper data type validation +5. **Modularity**: Break complex operations into smaller, reusable procedures +6. **Standards**: Always use qualified object names with schema prefix +7. **Versioning**: Use descriptive procedure names for different versions (e.g., `_V2` suffix) + [repository]: https://docs.microsoft.com/en-us/dotnet/architecture/microservices/microservice-ddd-cqrs-patterns/infrastructure-persistence-layer-design [dapper]: https://github.com/DapperLib/Dapper From 796ce3ecd495980f67e32c415e42f68a62ead659 Mon Sep 17 00:00:00 2001 From: mkincaid-bw Date: Wed, 13 Aug 2025 15:35:25 -0700 Subject: [PATCH 2/8] Ran prettier --- docs/contributing/code-style/sql.md | 83 +++++++++++++++++++++-------- 1 file changed, 61 insertions(+), 22 deletions(-) diff --git a/docs/contributing/code-style/sql.md b/docs/contributing/code-style/sql.md index 880fce07f..fdbc85d21 100644 --- a/docs/contributing/code-style/sql.md +++ b/docs/contributing/code-style/sql.md @@ -13,10 +13,12 @@ data from _Views_. ## File Organization ### Directory Structure -- **Schema-based organization**: Files are organized by domain/schema (Auth, Billing, SecretsManager, Vault, etc.) + +- **Schema-based organization**: Files are organized by domain/schema (Auth, Billing, + SecretsManager, Vault, etc.) - **Object type grouping**: Within each domain, files are grouped by type: - `Tables/` - Table definitions - - `Views/` - View definitions + - `Views/` - View definitions - `Stored Procedures/` - Stored procedure definitions - `Functions/` - User-defined functions - **Root-level objects**: Common objects are placed directly in `dbo/`: @@ -26,9 +28,12 @@ data from _Views_. - `User Defined Types/` - Custom data types ### File Naming Conventions -- **Stored Procedures**: `{EntityName}_{Action}.sql` (e.g., `User_Create.sql`, `Organization_ReadById.sql`) + +- **Stored Procedures**: `{EntityName}_{Action}.sql` (e.g., `User_Create.sql`, + `Organization_ReadById.sql`) - **Tables**: `{EntityName}.sql` (e.g., `User.sql`, `Organization.sql`) -- **Views**: `{EntityName}View.sql` or `{EntityName}{Purpose}View.sql` (e.g., `UserView.sql`, `OrganizationUserUserDetailsView.sql`) +- **Views**: `{EntityName}View.sql` or `{EntityName}{Purpose}View.sql` (e.g., `UserView.sql`, + `OrganizationUserUserDetailsView.sql`) - **Functions**: `{EntityName}{Purpose}.sql` (e.g., `UserCollectionDetails.sql`) - **User Defined Types**: `{TypeName}.sql` (e.g., `GuidIdArray.sql`) @@ -41,12 +46,16 @@ environment, but the scripts should support it. ### Tables #### Naming Conventions + - **Table Names**: PascalCase (e.g., `[dbo].[User]`, `[dbo].[AuthRequest]`) - **Column Names**: PascalCase (e.g., `[Id]`, `[CreationDate]`, `[MasterPasswordHash]`) - **Primary Keys**: `PK_{TableName}` (e.g., `[PK_User]`, `[PK_Organization]`) -- **Foreign Keys**: `FK_{TableName}_{ReferencedTable}` or `FK_{TableName}_{ColumnName}` (e.g., `[FK_AuthRequest_User]`) -- *PROPOSED: **Foreign Keys**: `FK_{TableName}_{ColumnName}__{ReferencedTable}_{ReferencedColumn}` (e.g., `FK_Device_UserId__User_Id`) - - **Note**: Constraint name limits: SQL Server (128 chars), PostgreSQL (63 chars), MySQL (64 chars), SQLite (unlimited) +- **Foreign Keys**: `FK_{TableName}_{ReferencedTable}` or `FK_{TableName}_{ColumnName}` (e.g., + `[FK_AuthRequest_User]`) +- \*PROPOSED: **Foreign Keys**: `FK_{TableName}_{ColumnName}__{ReferencedTable}_{ReferencedColumn}` + (e.g., `FK_Device_UserId__User_Id`) + - **Note**: Constraint name limits: SQL Server (128 chars), PostgreSQL (63 chars), MySQL (64 + chars), SQLite (unlimited) - **Default Constraints**: `DF_{TableName}_{ColumnName}` (e.g., `[DF_Organization_UseScim]`) #### Creating a table @@ -70,10 +79,11 @@ GO ``` ##### Column Definition Standards + - **Alignment**: Column names, data types, and nullability vertically aligned using spaces - **Data Types**: Use consistent type patterns: - `UNIQUEIDENTIFIER` for IDs - - `DATETIME2(7)` for timestamps + - `DATETIME2(7)` for timestamps - `NVARCHAR(n)` for Unicode text - `VARCHAR(n)` for ASCII text - `BIT` for boolean values @@ -191,15 +201,19 @@ GO ### Views #### Naming Conventions + - **View Names**: - `{EntityName}View` - - Used when the view maps closely to a single table, with little or no joins. (e.g., (e.g., `[dbo].[ApiKeyView]`) (from ApiKey)) - - `{EntityName}DetailsView` for complex views - - Used for views that combine multiple tables or add logic beyond a basic table select. These usually serve a specific display or reporting use case and are named to reflect the context. (e.g., `[dbo].[OrganizationUserDetailsView]`) + - Used when the view maps closely to a single table, with little or no joins. (e.g., (e.g., + `[dbo].[ApiKeyView]`) (from ApiKey)) + - `{EntityName}DetailsView` for complex views + - Used for views that combine multiple tables or add logic beyond a basic table select. These + usually serve a specific display or reporting use case and are named to reflect the context. + (e.g., `[dbo].[OrganizationUserDetailsView]`) - For more complex reads that join multiple tables: - - Create a view with a clear name tied to the main entity: - - `[dbo].[OrganizationUser_MemberAccessDetailsView]` + - Create a view with a clear name tied to the main entity: + - `[dbo].[OrganizationUser_MemberAccessDetailsView]` - Create a stored procedure that reads from it: - `[dbo].[OrganizationUser_ReadMemberAccessDetails]` @@ -243,6 +257,7 @@ GO ### Functions and stored procedures #### Naming Conventions + - **Stored Procedures**: `{EntityName}_{Action}` format (e.g., `[dbo].[User_ReadById]`) - EntityName: The main table or concept (e.g. User, Organization, Cipher) - Action: What the procedure does (e.g. Create, ReadById, DeleteMany) @@ -289,10 +304,12 @@ CREATE NONCLUSTERED INDEX [IX_OrganizationUser_UserIdOrganizationIdStatus] ``` #### Naming Conventions + - **Indexes**: `IX_{TableName}_{ColumnName(s)}` (e.g., `[IX_User_Email]`) - The name should clearly indicate the table and the columns being indexed. #### Index Best Practices + - Create indexes after table definition with `GO` separator - Use descriptive names following `IX_{TableName}_{ColumnName}` pattern - Include `INCLUDE` clause when beneficial for covering indexes @@ -302,25 +319,34 @@ CREATE NONCLUSTERED INDEX [IX_OrganizationUser_UserIdOrganizationIdStatus] ## General Naming Conventions ### Schema and Object Prefixes + - **Schema**: Use `[dbo]` prefix for all objects - **Object names**: Always use square brackets `[dbo].[TableName]` ### User Defined Types + - **User Defined Types**: `{TypeName}.sql` (e.g., `GuidIdArray.sql`) ## Code Formatting Standards ### General Formatting -The adage **`code is written once and read many times`** is one reason that creating easily readable code is very important. Once a developer has acclimated to the conventions and style in this document, they will be able to efficiently read any similarly written code by other developers. +The adage **`code is written once and read many times`** is one reason that creating easily readable +code is very important. Once a developer has acclimated to the conventions and style in this +document, they will be able to efficiently read any similarly written code by other developers. - **Indentation**: Use 4 spaces (not tabs) for all code files, including SQL -- **Keywords**: Use UPPERCASE for all SQL keywords (`CREATE`, `SELECT`, `FROM`, `WHERE`, `GROUP BY`, `ORDER BY`, `JOIN`, `ON`, `INTO`, `TOP`, etc.) +- **Keywords**: Use UPPERCASE for all SQL keywords (`CREATE`, `SELECT`, `FROM`, `WHERE`, `GROUP BY`, + `ORDER BY`, `JOIN`, `ON`, `INTO`, `TOP`, etc.) - **Object names**: Always use square brackets `[dbo].[TableName]` - **Line endings**: Use consistent line breaks with proper indentation -- **Trailing spaces**: Should be trimmed from the end of lines. Use `[ \t]+$` as a regex find/replace -- **Vertical lists**: Vertically list items as much as feasibly possible, and use consistent indentation to make vertical listing quick and easy. A vertical list is much easier to compare and makes code changes easily detectable -- **Whitespace**: Place one space, unless more are needed for good alignment, after each separating character, such as `+ - * / = & | %` and commas +- **Trailing spaces**: Should be trimmed from the end of lines. Use `[ \t]+$` as a regex + find/replace +- **Vertical lists**: Vertically list items as much as feasibly possible, and use consistent + indentation to make vertical listing quick and easy. A vertical list is much easier to compare and + makes code changes easily detectable +- **Whitespace**: Place one space, unless more are needed for good alignment, after each separating + character, such as `+ - * / = & | %` and commas - **Blank lines**: Separate sections of code with at least one blank line - **Commas**: Commas should be placed at the right end of the line - **Join clauses and logical operators**: Should be placed at the right end of the line @@ -328,6 +354,7 @@ The adage **`code is written once and read many times`** is one reason that crea ### Stored Procedures #### Basic Structure + ```sql CREATE PROCEDURE [dbo].[EntityName_Action] @Parameter1 DATATYPE, @@ -343,12 +370,14 @@ END ``` #### Parameter Declaration + - One parameter per line - Align parameters with consistent indentation (4 spaces after procedure name) - Default values on same line as parameter - OUTPUT parameters clearly marked #### SELECT Statements + - `SELECT` keyword on its own line - Column names indented (4 spaces) - One column per line for multi-column selects @@ -371,6 +400,7 @@ WHERE ``` #### INSERT Statements + - Column list in parentheses, one column per line - VALUES clause with parameters aligned - Proper indentation for readability @@ -391,6 +421,7 @@ VALUES ``` #### UPDATE Statements + - `UPDATE` and table name on same line - `SET` clause with each column assignment on separate line - `WHERE` clause clearly separated @@ -409,6 +440,7 @@ WHERE ### Views #### Simple Views + ```sql CREATE VIEW [dbo].[ViewName] AS @@ -419,6 +451,7 @@ FROM ``` #### Complex Views + ```sql CREATE VIEW [dbo].[ComplexViewName] AS @@ -442,10 +475,12 @@ WHERE ### Functions #### Naming Conventions + - **Function Names**: `[Schema].[FunctionName]` (e.g., `[dbo].[UserCollectionDetails]`). - - The name should describe what the function returns + - The name should describe what the function returns #### Table-Valued Functions + ```sql CREATE FUNCTION [dbo].[FunctionName](@Parameter DATATYPE) RETURNS TABLE @@ -465,8 +500,9 @@ WHERE ``` ### User Defined Types + - **Naming**: `[Schema].[TypeName]` (e.g., `[dbo].[GuidIdArray]`). - - The name should describe the type. + - The name should describe the type. ```sql CREATE TYPE [dbo].[TypeName] AS TABLE ( @@ -478,18 +514,21 @@ CREATE TYPE [dbo].[TypeName] AS TABLE ( ## Common Patterns ### CRUD Operations + - **Create**: `{EntityName}_Create` procedures -- **Read**: `{EntityName}_ReadById`, `{EntityName}_ReadBy{Criteria}` procedures +- **Read**: `{EntityName}_ReadById`, `{EntityName}_ReadBy{Criteria}` procedures - **Update**: `{EntityName}_Update` procedures - **Delete**: `{EntityName}_DeleteById`, `{EntityName}_Delete` procedures ### Parameter Patterns + - `@Id UNIQUEIDENTIFIER` for entity identifiers - `@UserId UNIQUEIDENTIFIER` for user references - `@OrganizationId UNIQUEIDENTIFIER` for organization references - `@CreationDate DATETIME2(7)` and `@RevisionDate DATETIME2(7)` for timestamps ### Error Handling + - Use `SET NOCOUNT ON` in stored procedures - Implement appropriate transaction handling where needed - Follow consistent error reporting patterns @@ -505,7 +544,7 @@ CREATE TYPE [dbo].[TypeName] AS TABLE ( ## Best Practices 1. **Consistency**: Follow established patterns throughout the codebase -2. **Readability**: Prioritize code readability and maintainability +2. **Readability**: Prioritize code readability and maintainability 3. **Performance**: Consider index usage and query optimization 4. **Security**: Use parameterized queries and proper data type validation 5. **Modularity**: Break complex operations into smaller, reusable procedures From 8ed23547af07645e39801d2db9f72590437541c5 Mon Sep 17 00:00:00 2001 From: mkincaid-bw Date: Wed, 20 Aug 2025 13:44:04 -0700 Subject: [PATCH 3/8] Update SQL code formatting standards and conventions - Standardized foreign key naming to include referenced table/column format - Added comprehensive SELECT statement formatting guidelines with table aliases - Clarified online index creation policy for self-hosted compatibility - Updated INSERT/UPDATE statement formatting examples with consistent parentheses alignment - Enhanced stored procedure and UDT formatting standards - Reorganized sections for better logical flow and readability --- docs/contributing/code-style/sql.md | 137 ++++++++++++++-------------- 1 file changed, 68 insertions(+), 69 deletions(-) diff --git a/docs/contributing/code-style/sql.md b/docs/contributing/code-style/sql.md index fdbc85d21..359419e48 100644 --- a/docs/contributing/code-style/sql.md +++ b/docs/contributing/code-style/sql.md @@ -37,6 +37,27 @@ data from _Views_. - **Functions**: `{EntityName}{Purpose}.sql` (e.g., `UserCollectionDetails.sql`) - **User Defined Types**: `{TypeName}.sql` (e.g., `GuidIdArray.sql`) +## Code Formatting Standards + +### General Formatting + +The adage **`Code is read much more often than it is written`** is one reason that creating easily readable +code is very important. + +- **Indentation**: Use 4 spaces (not tabs) for all SQL code files. +- **Keywords**: Use UPPERCASE for all SQL keywords (`CREATE`, `SELECT`, `FROM`, `WHERE`, `GROUP BY`, + `ORDER BY`, `JOIN`, `ON`, `INTO`, `TOP`, etc.) +- **Object names**: Always use square brackets `[dbo].[TableName]` +- **Line endings**: Use consistent line breaks with proper indentation +- **Trailing spaces**: Should be trimmed from the end of lines. Use `[ \t]+$` as a regex + find/replace +- **Vertical lists**: Vertically list items as much as feasibly possible, and use consistent + indentation to make vertical listing quick and easy. A vertical list is much easier to compare and + makes code changes easily detectable +- **Blank lines**: Separate sections of code with at least one blank line +- **Commas**: Commas should be placed at the right end of the line +- **Parentheses**: Parentheses should be vertically aligned with spanning multiple lines + ## Deployment Scripts There are specific ways deployment scripts should be structured. The goal for these standards is to @@ -50,12 +71,9 @@ environment, but the scripts should support it. - **Table Names**: PascalCase (e.g., `[dbo].[User]`, `[dbo].[AuthRequest]`) - **Column Names**: PascalCase (e.g., `[Id]`, `[CreationDate]`, `[MasterPasswordHash]`) - **Primary Keys**: `PK_{TableName}` (e.g., `[PK_User]`, `[PK_Organization]`) -- **Foreign Keys**: `FK_{TableName}_{ReferencedTable}` or `FK_{TableName}_{ColumnName}` (e.g., - `[FK_AuthRequest_User]`) -- \*PROPOSED: **Foreign Keys**: `FK_{TableName}_{ColumnName}__{ReferencedTable}_{ReferencedColumn}` +- **Foreign Keys**: `FK_{TableName}_{ColumnName}__{ReferencedTable}_{ReferencedColumn}` (e.g., `FK_Device_UserId__User_Id`) - - **Note**: Constraint name limits: SQL Server (128 chars), PostgreSQL (63 chars), MySQL (64 - chars), SQLite (unlimited) + - **Note**: SQL SErver limits constraint names to 128 characters. - **Default Constraints**: `DF_{TableName}_{ColumnName}` (e.g., `[DF_Organization_UseScim]`) #### Creating a table @@ -65,8 +83,8 @@ When creating a table, you must first check if the table exists: ```sql IF OBJECT_ID('[dbo].[{table_name}]') IS NULL BEGIN - CREATE TABLE [dbo].[{table_name}] ( - [Id] UNIQUEIDENTIFIER NOT NULL, + CREATE TABLE [dbo].[{table_name}] + ( [Id] UNIQUEIDENTIFIER NOT NULL, [Column1] DATATYPE NOT NULL, [Column2] DATATYPE NULL, [CreationDate] DATETIME2(7) NOT NULL, @@ -163,7 +181,7 @@ BEGIN ALTER TABLE [dbo].[Column] ADD - [Column] INT NOT NULL CONSTRAINT D_Table_Column DEFAULT 0 + [Column] INT NOT NULL CONSTRAINT DF_Table_Column DEFAULT 0 END GO ``` @@ -287,10 +305,12 @@ GO ### Creating or modifying an index When creating indexes, especially on heavily used tables, our production database can easily become -offline, unusable, hit 100% CPU and many other bad behaviors. It is often best to do this using -online index builds so as not to lock the underlying table. This may cause the index operation to -take longer, but you will not create an underlying schema table lock which prevents all reads and -connections to the table and instead only locks the table of updates during the operation. +offline, unusable, hit 100% CPU and many other bad behaviors. Our production database is configured to +do online index builds by default, (so as not to lock the underlying table), so you should **_not_** specify + `ONLINE = ON`, as this may cause failures on self-hosted SQL Server editions that do not support online + index rebuilds. Online index creation may cause the index operation to take longer, but it will not + create an underlying schema table lock which prevents all reads and connections to the table and + instead only locks the table of updates during the operation. A good example is when creating an index on `dbo.Cipher` or `dbo.OrganizationUser`, those are heavy-read tables and the locks can cause exceptionally high CPU, wait times and worker exhaustion @@ -300,7 +320,6 @@ in Azure SQL. CREATE NONCLUSTERED INDEX [IX_OrganizationUser_UserIdOrganizationIdStatus] ON [dbo].[OrganizationUser]([UserId] ASC, [OrganizationId] ASC, [Status] ASC) INCLUDE ([AccessAll]) - WITH (ONLINE = ON); -- ** THIS ENSURES ONLINE ** ``` #### Naming Conventions @@ -310,11 +329,10 @@ CREATE NONCLUSTERED INDEX [IX_OrganizationUser_UserIdOrganizationIdStatus] #### Index Best Practices -- Create indexes after table definition with `GO` separator -- Use descriptive names following `IX_{TableName}_{ColumnName}` pattern -- Include `INCLUDE` clause when beneficial for covering indexes -- Use filtered indexes with `WHERE` clause when appropriate -- Consider `ONLINE = ON` for production deployments on heavily used tables +- Create indexes after table definition with `GO` separator. +- Use descriptive names following `IX_{TableName}_{ColumnName}` pattern. +- Include `INCLUDE` clause when beneficial for covering indexes. +- Use filtered indexes with `WHERE` clause when appropriate. ## General Naming Conventions @@ -325,31 +343,37 @@ CREATE NONCLUSTERED INDEX [IX_OrganizationUser_UserIdOrganizationIdStatus] ### User Defined Types -- **User Defined Types**: `{TypeName}.sql` (e.g., `GuidIdArray.sql`) +- **User Defined Types**: Filenames should be `{TypeName}.sql` (e.g., `GuidIdArray.sql`) -## Code Formatting Standards +### SELECT Statements -### General Formatting +- `SELECT` keyword on its own line +- Column names indented (4 spaces) +- One column per line for multi-column selects +- Callout the specific table/alias for where a column is from when joining to other tables +- `FROM` keyword on separate line, aligned with `SELECT` +- `FROM` clause indented (4 spaces) + - Use aliases for table names when joining to other tables +- `JOIN` keywords on separate line, aligned with `FROM` + - Use full join specfications (`INNER JOIN` vs `JOIN`, `LEFT OUTER JOIN` vs `LEFT JOIN`, etc) +- `JOIN` clauses indented to align with table/column name(s) +- `WHERE` keyword on separate line, aligned with `FROM`/`JOIN` +- `WHERE` clause on separate lines, indented to align with table/column name(s) -The adage **`code is written once and read many times`** is one reason that creating easily readable -code is very important. Once a developer has acclimated to the conventions and style in this -document, they will be able to efficiently read any similarly written code by other developers. +```sql +SELECT + U.[Id], + U.[Name], + U.[Email], + OU.[OrganizationId] +FROM + [dbo].[User] U +INNER JOIN + [dbo].[OrganizationUser] OU ON U.[Id] = OU.[UserId] +WHERE + U.[Enabled] = 1 +``` -- **Indentation**: Use 4 spaces (not tabs) for all code files, including SQL -- **Keywords**: Use UPPERCASE for all SQL keywords (`CREATE`, `SELECT`, `FROM`, `WHERE`, `GROUP BY`, - `ORDER BY`, `JOIN`, `ON`, `INTO`, `TOP`, etc.) -- **Object names**: Always use square brackets `[dbo].[TableName]` -- **Line endings**: Use consistent line breaks with proper indentation -- **Trailing spaces**: Should be trimmed from the end of lines. Use `[ \t]+$` as a regex - find/replace -- **Vertical lists**: Vertically list items as much as feasibly possible, and use consistent - indentation to make vertical listing quick and easy. A vertical list is much easier to compare and - makes code changes easily detectable -- **Whitespace**: Place one space, unless more are needed for good alignment, after each separating - character, such as `+ - * / = & | %` and commas -- **Blank lines**: Separate sections of code with at least one blank line -- **Commas**: Commas should be placed at the right end of the line -- **Join clauses and logical operators**: Should be placed at the right end of the line ### Stored Procedures @@ -376,29 +400,6 @@ END - Default values on same line as parameter - OUTPUT parameters clearly marked -#### SELECT Statements - -- `SELECT` keyword on its own line -- Column names indented (4 spaces) -- One column per line for multi-column selects -- `FROM` clause on separate line, aligned with `SELECT` -- `JOIN` clauses indented to align with table names -- `WHERE` clause on separate line, aligned with `FROM` - -```sql -SELECT - U.[Id], - U.[Name], - U.[Email], - OU.[OrganizationId] -FROM - [dbo].[User] U -INNER JOIN - [dbo].[OrganizationUser] OU ON U.[Id] = OU.[UserId] -WHERE - U.[Enabled] = 1 -``` - #### INSERT Statements - Column list in parentheses, one column per line @@ -407,14 +408,12 @@ WHERE ```sql INSERT INTO [dbo].[TableName] -( - [Column1], +( [Column1], [Column2], [Column3] ) VALUES -( - @Parameter1, +( @Parameter1, @Parameter2, @Parameter3 ) @@ -422,7 +421,7 @@ VALUES #### UPDATE Statements -- `UPDATE` and table name on same line +- `UPDATE` and table name on different lines - `SET` clause with each column assignment on separate line - `WHERE` clause clearly separated @@ -505,8 +504,8 @@ WHERE - The name should describe the type. ```sql -CREATE TYPE [dbo].[TypeName] AS TABLE ( - [Column1] DATATYPE NOT NULL, +CREATE TYPE [dbo].[TypeName] AS TABLE +( [Column1] DATATYPE NOT NULL, [Column2] DATATYPE NOT NULL ); ``` From cd06ac91d9694520abd000fe831ebcecac98dc27 Mon Sep 17 00:00:00 2001 From: mkincaid-bw Date: Wed, 20 Aug 2025 13:48:54 -0700 Subject: [PATCH 4/8] Ran prettier --- docs/contributing/code-style/sql.md | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/docs/contributing/code-style/sql.md b/docs/contributing/code-style/sql.md index 359419e48..1ba13bb34 100644 --- a/docs/contributing/code-style/sql.md +++ b/docs/contributing/code-style/sql.md @@ -41,8 +41,8 @@ data from _Views_. ### General Formatting -The adage **`Code is read much more often than it is written`** is one reason that creating easily readable -code is very important. +The adage **`Code is read much more often than it is written`** is one reason that creating easily +readable code is very important. - **Indentation**: Use 4 spaces (not tabs) for all SQL code files. - **Keywords**: Use UPPERCASE for all SQL keywords (`CREATE`, `SELECT`, `FROM`, `WHERE`, `GROUP BY`, @@ -71,9 +71,9 @@ environment, but the scripts should support it. - **Table Names**: PascalCase (e.g., `[dbo].[User]`, `[dbo].[AuthRequest]`) - **Column Names**: PascalCase (e.g., `[Id]`, `[CreationDate]`, `[MasterPasswordHash]`) - **Primary Keys**: `PK_{TableName}` (e.g., `[PK_User]`, `[PK_Organization]`) -- **Foreign Keys**: `FK_{TableName}_{ColumnName}__{ReferencedTable}_{ReferencedColumn}` - (e.g., `FK_Device_UserId__User_Id`) - - **Note**: SQL SErver limits constraint names to 128 characters. +- **Foreign Keys**: `FK_{TableName}_{ColumnName}__{ReferencedTable}_{ReferencedColumn}` (e.g., + `FK_Device_UserId__User_Id`) + - **Note**: SQL SErver limits constraint names to 128 characters. - **Default Constraints**: `DF_{TableName}_{ColumnName}` (e.g., `[DF_Organization_UseScim]`) #### Creating a table @@ -83,7 +83,7 @@ When creating a table, you must first check if the table exists: ```sql IF OBJECT_ID('[dbo].[{table_name}]') IS NULL BEGIN - CREATE TABLE [dbo].[{table_name}] + CREATE TABLE [dbo].[{table_name}] ( [Id] UNIQUEIDENTIFIER NOT NULL, [Column1] DATATYPE NOT NULL, [Column2] DATATYPE NULL, @@ -305,12 +305,12 @@ GO ### Creating or modifying an index When creating indexes, especially on heavily used tables, our production database can easily become -offline, unusable, hit 100% CPU and many other bad behaviors. Our production database is configured to -do online index builds by default, (so as not to lock the underlying table), so you should **_not_** specify - `ONLINE = ON`, as this may cause failures on self-hosted SQL Server editions that do not support online - index rebuilds. Online index creation may cause the index operation to take longer, but it will not - create an underlying schema table lock which prevents all reads and connections to the table and - instead only locks the table of updates during the operation. +offline, unusable, hit 100% CPU and many other bad behaviors. Our production database is configured +to do online index builds by default, (so as not to lock the underlying table), so you should +**_not_** specify `ONLINE = ON`, as this may cause failures on self-hosted SQL Server editions that +do not support online index rebuilds. Online index creation may cause the index operation to take +longer, but it will not create an underlying schema table lock which prevents all reads and +connections to the table and instead only locks the table of updates during the operation. A good example is when creating an index on `dbo.Cipher` or `dbo.OrganizationUser`, those are heavy-read tables and the locks can cause exceptionally high CPU, wait times and worker exhaustion @@ -353,9 +353,9 @@ CREATE NONCLUSTERED INDEX [IX_OrganizationUser_UserIdOrganizationIdStatus] - Callout the specific table/alias for where a column is from when joining to other tables - `FROM` keyword on separate line, aligned with `SELECT` - `FROM` clause indented (4 spaces) - - Use aliases for table names when joining to other tables + - Use aliases for table names when joining to other tables - `JOIN` keywords on separate line, aligned with `FROM` - - Use full join specfications (`INNER JOIN` vs `JOIN`, `LEFT OUTER JOIN` vs `LEFT JOIN`, etc) + - Use full join specfications (`INNER JOIN` vs `JOIN`, `LEFT OUTER JOIN` vs `LEFT JOIN`, etc) - `JOIN` clauses indented to align with table/column name(s) - `WHERE` keyword on separate line, aligned with `FROM`/`JOIN` - `WHERE` clause on separate lines, indented to align with table/column name(s) @@ -374,7 +374,6 @@ WHERE U.[Enabled] = 1 ``` - ### Stored Procedures #### Basic Structure From 7f7a43099610aec5bf79bf1588058fa0fd5def07 Mon Sep 17 00:00:00 2001 From: Mark Kincaid Date: Wed, 20 Aug 2025 14:22:16 -0700 Subject: [PATCH 5/8] Fixed type-o --- docs/contributing/code-style/sql.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/contributing/code-style/sql.md b/docs/contributing/code-style/sql.md index 1ba13bb34..fcca298ee 100644 --- a/docs/contributing/code-style/sql.md +++ b/docs/contributing/code-style/sql.md @@ -355,7 +355,7 @@ CREATE NONCLUSTERED INDEX [IX_OrganizationUser_UserIdOrganizationIdStatus] - `FROM` clause indented (4 spaces) - Use aliases for table names when joining to other tables - `JOIN` keywords on separate line, aligned with `FROM` - - Use full join specfications (`INNER JOIN` vs `JOIN`, `LEFT OUTER JOIN` vs `LEFT JOIN`, etc) + - Use full join specifications (`INNER JOIN` vs `JOIN`, `LEFT OUTER JOIN` vs `LEFT JOIN`, etc) - `JOIN` clauses indented to align with table/column name(s) - `WHERE` keyword on separate line, aligned with `FROM`/`JOIN` - `WHERE` clause on separate lines, indented to align with table/column name(s) From ae9e145ef3739843dff778070ccfe46660e049bf Mon Sep 17 00:00:00 2001 From: Mark Kincaid Date: Mon, 25 Aug 2025 10:31:22 -0700 Subject: [PATCH 6/8] Made requested changes --- docs/contributing/code-style/sql.md | 99 +++++++++++++++++++---------- 1 file changed, 65 insertions(+), 34 deletions(-) diff --git a/docs/contributing/code-style/sql.md b/docs/contributing/code-style/sql.md index fcca298ee..621351b74 100644 --- a/docs/contributing/code-style/sql.md +++ b/docs/contributing/code-style/sql.md @@ -33,7 +33,7 @@ data from _Views_. `Organization_ReadById.sql`) - **Tables**: `{EntityName}.sql` (e.g., `User.sql`, `Organization.sql`) - **Views**: `{EntityName}View.sql` or `{EntityName}{Purpose}View.sql` (e.g., `UserView.sql`, - `OrganizationUserUserDetailsView.sql`) + `ApiKeyDetailsView.sql`) - **Functions**: `{EntityName}{Purpose}.sql` (e.g., `UserCollectionDetails.sql`) - **User Defined Types**: `{TypeName}.sql` (e.g., `GuidIdArray.sql`) @@ -41,10 +41,7 @@ data from _Views_. ### General Formatting -The adage **`Code is read much more often than it is written`** is one reason that creating easily -readable code is very important. - -- **Indentation**: Use 4 spaces (not tabs) for all SQL code files. +- **Indentation**: Use 4 spaces (not tabs) for all SQL code files - **Keywords**: Use UPPERCASE for all SQL keywords (`CREATE`, `SELECT`, `FROM`, `WHERE`, `GROUP BY`, `ORDER BY`, `JOIN`, `ON`, `INTO`, `TOP`, etc.) - **Object names**: Always use square brackets `[dbo].[TableName]` @@ -71,9 +68,7 @@ environment, but the scripts should support it. - **Table Names**: PascalCase (e.g., `[dbo].[User]`, `[dbo].[AuthRequest]`) - **Column Names**: PascalCase (e.g., `[Id]`, `[CreationDate]`, `[MasterPasswordHash]`) - **Primary Keys**: `PK_{TableName}` (e.g., `[PK_User]`, `[PK_Organization]`) -- **Foreign Keys**: `FK_{TableName}_{ColumnName}__{ReferencedTable}_{ReferencedColumn}` (e.g., - `FK_Device_UserId__User_Id`) - - **Note**: SQL SErver limits constraint names to 128 characters. +- **Foreign Keys**: `FK_{TableName}_{ReferencedTable}` (e.g., `FK_Device_User`) - **Default Constraints**: `DF_{TableName}_{ColumnName}` (e.g., `[DF_Organization_UseScim]`) #### Creating a table @@ -226,14 +221,14 @@ GO `[dbo].[ApiKeyView]`) (from ApiKey)) - `{EntityName}DetailsView` for complex views - Used for views that combine multiple tables or add logic beyond a basic table select. These - usually serve a specific display or reporting use case and are named to reflect the context. + usually serve a specific display or reporting use case and are named to reflect the context (e.g., `[dbo].[OrganizationUserDetailsView]`) - For more complex reads that join multiple tables: - Create a view with a clear name tied to the main entity: - `[dbo].[OrganizationUser_MemberAccessDetailsView]` - Create a stored procedure that reads from it: - - `[dbo].[OrganizationUser_ReadMemberAccessDetails]` + - `[dbo].[MemberAccessDetails_ReadByUserId]` #### Creating or modifying a view @@ -307,10 +302,10 @@ GO When creating indexes, especially on heavily used tables, our production database can easily become offline, unusable, hit 100% CPU and many other bad behaviors. Our production database is configured to do online index builds by default, (so as not to lock the underlying table), so you should -**_not_** specify `ONLINE = ON`, as this may cause failures on self-hosted SQL Server editions that -do not support online index rebuilds. Online index creation may cause the index operation to take -longer, but it will not create an underlying schema table lock which prevents all reads and -connections to the table and instead only locks the table of updates during the operation. +**_not_** specify `ONLINE = ON`, as this may cause failures on some SQL Server editions that do not +support online index rebuilds. Online index creation may cause the index operation to take longer, +but it will not create an underlying schema table lock which prevents all reads and connections to +the table and instead only locks the table of updates during the operation. A good example is when creating an index on `dbo.Cipher` or `dbo.OrganizationUser`, those are heavy-read tables and the locks can cause exceptionally high CPU, wait times and worker exhaustion @@ -325,14 +320,14 @@ CREATE NONCLUSTERED INDEX [IX_OrganizationUser_UserIdOrganizationIdStatus] #### Naming Conventions - **Indexes**: `IX_{TableName}_{ColumnName(s)}` (e.g., `[IX_User_Email]`) - - The name should clearly indicate the table and the columns being indexed. + - The name should clearly indicate the table and the columns being indexed #### Index Best Practices -- Create indexes after table definition with `GO` separator. -- Use descriptive names following `IX_{TableName}_{ColumnName}` pattern. -- Include `INCLUDE` clause when beneficial for covering indexes. -- Use filtered indexes with `WHERE` clause when appropriate. +- Create indexes after table definition with `GO` separator +- Use descriptive names following `IX_{TableName}_{ColumnName}` pattern +- Include `INCLUDE` clause when beneficial for covering indexes +- Use filtered indexes with `WHERE` clause when appropriate ## General Naming Conventions @@ -344,6 +339,7 @@ CREATE NONCLUSTERED INDEX [IX_OrganizationUser_UserIdOrganizationIdStatus] ### User Defined Types - **User Defined Types**: Filenames should be `{TypeName}.sql` (e.g., `GuidIdArray.sql`) + - Note: Use sparingly as they cause downstream maintenance and performance problems ### SELECT Statements @@ -395,10 +391,17 @@ END #### Parameter Declaration - One parameter per line -- Align parameters with consistent indentation (4 spaces after procedure name) +- Align parameters with consistent indentation (4 spaces) - Default values on same line as parameter - OUTPUT parameters clearly marked +Note: When adding parameters to an existing stored procedure, a default value must be specified to +ensure backward compatibility and ensure existing code can be called without modification. + +Also use `SET NOCOUNT ON` to prevent the automatic return of row count messages, which improves +performance and ensures consistent behavior across different client applications that might handle +these messages differently. + #### INSERT Statements - Column list in parentheses, one column per line @@ -458,10 +461,10 @@ SELECT T1.[Column2], T2.[Column3], CASE - WHEN T1.[Status] = 1 - THEN 'Active' - ELSE 'Inactive' - END [StatusText] + WHEN T2.[Column4] IS NOT NULL + THEN 1 + ELSE 0 + END AS ColumnAlias FROM [dbo].[Table1] T1 LEFT JOIN @@ -474,7 +477,7 @@ WHERE #### Naming Conventions -- **Function Names**: `[Schema].[FunctionName]` (e.g., `[dbo].[UserCollectionDetails]`). +- **Function Names**: `[Schema].[FunctionName]` (e.g., `[dbo].[UserCollectionDetails]`) - The name should describe what the function returns #### Table-Valued Functions @@ -499,8 +502,8 @@ WHERE ### User Defined Types -- **Naming**: `[Schema].[TypeName]` (e.g., `[dbo].[GuidIdArray]`). - - The name should describe the type. +- **Naming**: `[Schema].[TypeName]` (e.g., `[dbo].[GuidIdArray]`) + - The name should describe the type ```sql CREATE TYPE [dbo].[TypeName] AS TABLE @@ -515,21 +518,49 @@ CREATE TYPE [dbo].[TypeName] AS TABLE - **Create**: `{EntityName}_Create` procedures - **Read**: `{EntityName}_ReadById`, `{EntityName}_ReadBy{Criteria}` procedures +- **Read Many**: `{EntityName}_ReadManyByIds`, `{EntityName}_ReadManyBy{Criteria}` procedures - **Update**: `{EntityName}_Update` procedures - **Delete**: `{EntityName}_DeleteById`, `{EntityName}_Delete` procedures -### Parameter Patterns - -- `@Id UNIQUEIDENTIFIER` for entity identifiers -- `@UserId UNIQUEIDENTIFIER` for user references -- `@OrganizationId UNIQUEIDENTIFIER` for organization references -- `@CreationDate DATETIME2(7)` and `@RevisionDate DATETIME2(7)` for timestamps - ### Error Handling - Use `SET NOCOUNT ON` in stored procedures - Implement appropriate transaction handling where needed - Follow consistent error reporting patterns + - Business logic should not be in stored procedures, but there may be times when it makes sense to + do error handling in other scripts (migrations, one-off data scrubs) + +```sql +BEGIN TRY + BEGIN TRANSACTION; + + UPDATE + [dbo].[TableName] + SET + [Column1] = 'NewValue' + WHERE + [Id] = 'IdValue' + + + UPDATE + [dbo].[TableName2] + SET + [Column1] = 'NewValue' + WHERE + [Id] = 'IdValue' + + COMMIT TRANSACTION; + +END TRY +BEGIN CATCH + + IF @@TRANCOUNT > 0 + ROLLBACK TRANSACTION; + + THROW; + +END CATCH; +``` ## Comments and Documentation From 7b6ba0e29e2e5200507b2a725361bbe9c9989afc Mon Sep 17 00:00:00 2001 From: Mark Kincaid Date: Mon, 25 Aug 2025 14:30:26 -0700 Subject: [PATCH 7/8] Changed header capitalization style --- docs/contributing/code-style/sql.md | 66 ++++++++++++++--------------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/docs/contributing/code-style/sql.md b/docs/contributing/code-style/sql.md index 621351b74..6b8834925 100644 --- a/docs/contributing/code-style/sql.md +++ b/docs/contributing/code-style/sql.md @@ -10,9 +10,9 @@ We use the [Repository pattern][repository] with the MSSQL repositories being wr [Dapper][dapper]. Each repository method in turn calls a _Stored Procedure_, which primarily fetches data from _Views_. -## File Organization +## File organization -### Directory Structure +### Directory structure - **Schema-based organization**: Files are organized by domain/schema (Auth, Billing, SecretsManager, Vault, etc.) @@ -27,7 +27,7 @@ data from _Views_. - `Views/` - General views - `User Defined Types/` - Custom data types -### File Naming Conventions +### File naming conventions - **Stored Procedures**: `{EntityName}_{Action}.sql` (e.g., `User_Create.sql`, `Organization_ReadById.sql`) @@ -37,9 +37,9 @@ data from _Views_. - **Functions**: `{EntityName}{Purpose}.sql` (e.g., `UserCollectionDetails.sql`) - **User Defined Types**: `{TypeName}.sql` (e.g., `GuidIdArray.sql`) -## Code Formatting Standards +## Code formatting standards -### General Formatting +### General formatting - **Indentation**: Use 4 spaces (not tabs) for all SQL code files - **Keywords**: Use UPPERCASE for all SQL keywords (`CREATE`, `SELECT`, `FROM`, `WHERE`, `GROUP BY`, @@ -55,7 +55,7 @@ data from _Views_. - **Commas**: Commas should be placed at the right end of the line - **Parentheses**: Parentheses should be vertically aligned with spanning multiple lines -## Deployment Scripts +## Deployment scripts There are specific ways deployment scripts should be structured. The goal for these standards is to ensure that the scripts should be re-runnable. We never intend to run scripts multiple times on an @@ -63,7 +63,7 @@ environment, but the scripts should support it. ### Tables -#### Naming Conventions +#### Naming conventions - **Table Names**: PascalCase (e.g., `[dbo].[User]`, `[dbo].[AuthRequest]`) - **Column Names**: PascalCase (e.g., `[Id]`, `[CreationDate]`, `[MasterPasswordHash]`) @@ -91,7 +91,7 @@ END GO ``` -##### Column Definition Standards +#### Column definition standards - **Alignment**: Column names, data types, and nullability vertically aligned using spaces - **Data Types**: Use consistent type patterns: @@ -213,7 +213,7 @@ GO ### Views -#### Naming Conventions +#### Naming conventions - **View Names**: - `{EntityName}View` @@ -269,9 +269,9 @@ GO ### Functions and stored procedures -#### Naming Conventions +#### Naming conventions -- **Stored Procedures**: `{EntityName}_{Action}` format (e.g., `[dbo].[User_ReadById]`) +- **Stored procedures**: `{EntityName}_{Action}` format (e.g., `[dbo].[User_ReadById]`) - EntityName: The main table or concept (e.g. User, Organization, Cipher) - Action: What the procedure does (e.g. Create, ReadById, DeleteMany) - **Parameters**: Start with `@` and use PascalCase (e.g., `@UserId`, `@OrganizationId`) @@ -317,31 +317,31 @@ CREATE NONCLUSTERED INDEX [IX_OrganizationUser_UserIdOrganizationIdStatus] INCLUDE ([AccessAll]) ``` -#### Naming Conventions +#### Naming conventions - **Indexes**: `IX_{TableName}_{ColumnName(s)}` (e.g., `[IX_User_Email]`) - The name should clearly indicate the table and the columns being indexed -#### Index Best Practices +#### Index best practices - Create indexes after table definition with `GO` separator - Use descriptive names following `IX_{TableName}_{ColumnName}` pattern - Include `INCLUDE` clause when beneficial for covering indexes - Use filtered indexes with `WHERE` clause when appropriate -## General Naming Conventions +## General naming conventions -### Schema and Object Prefixes +### Schema and object prefixes - **Schema**: Use `[dbo]` prefix for all objects - **Object names**: Always use square brackets `[dbo].[TableName]` -### User Defined Types +### User defined types - **User Defined Types**: Filenames should be `{TypeName}.sql` (e.g., `GuidIdArray.sql`) - Note: Use sparingly as they cause downstream maintenance and performance problems -### SELECT Statements +### Select statements - `SELECT` keyword on its own line - Column names indented (4 spaces) @@ -370,9 +370,9 @@ WHERE U.[Enabled] = 1 ``` -### Stored Procedures +### Stored procedures -#### Basic Structure +#### Basic structure ```sql CREATE PROCEDURE [dbo].[EntityName_Action] @@ -388,7 +388,7 @@ BEGIN END ``` -#### Parameter Declaration +#### Parameter declaration - One parameter per line - Align parameters with consistent indentation (4 spaces) @@ -402,7 +402,7 @@ Also use `SET NOCOUNT ON` to prevent the automatic return of row count messages, performance and ensures consistent behavior across different client applications that might handle these messages differently. -#### INSERT Statements +#### Insert statements - Column list in parentheses, one column per line - VALUES clause with parameters aligned @@ -421,7 +421,7 @@ VALUES ) ``` -#### UPDATE Statements +#### Update statements - `UPDATE` and table name on different lines - `SET` clause with each column assignment on separate line @@ -440,7 +440,7 @@ WHERE ### Views -#### Simple Views +#### Simple views ```sql CREATE VIEW [dbo].[ViewName] @@ -451,7 +451,7 @@ FROM [dbo].[TableName] ``` -#### Complex Views +#### Complex views ```sql CREATE VIEW [dbo].[ComplexViewName] @@ -475,12 +475,12 @@ WHERE ### Functions -#### Naming Conventions +#### Naming conventions -- **Function Names**: `[Schema].[FunctionName]` (e.g., `[dbo].[UserCollectionDetails]`) +- **Function names**: `[Schema].[FunctionName]` (e.g., `[dbo].[UserCollectionDetails]`) - The name should describe what the function returns -#### Table-Valued Functions +#### Table-valued functions ```sql CREATE FUNCTION [dbo].[FunctionName](@Parameter DATATYPE) @@ -500,7 +500,7 @@ WHERE [FilterColumn] = @Parameter ``` -### User Defined Types +### User defined types - **Naming**: `[Schema].[TypeName]` (e.g., `[dbo].[GuidIdArray]`) - The name should describe the type @@ -512,9 +512,9 @@ CREATE TYPE [dbo].[TypeName] AS TABLE ); ``` -## Common Patterns +## Common patterns -### CRUD Operations +### CRUD operations - **Create**: `{EntityName}_Create` procedures - **Read**: `{EntityName}_ReadById`, `{EntityName}_ReadBy{Criteria}` procedures @@ -522,7 +522,7 @@ CREATE TYPE [dbo].[TypeName] AS TABLE - **Update**: `{EntityName}_Update` procedures - **Delete**: `{EntityName}_DeleteById`, `{EntityName}_Delete` procedures -### Error Handling +### Error handling - Use `SET NOCOUNT ON` in stored procedures - Implement appropriate transaction handling where needed @@ -562,7 +562,7 @@ BEGIN CATCH END CATCH; ``` -## Comments and Documentation +## Comments and documentation - Use `--` for single-line comments - Add comments for complex business logic and the reason for a command or block of code @@ -570,7 +570,7 @@ END CATCH; - Provide brief explanations for complex CASE statements or calculations - Don't comment unnecessarily, such as commenting that an insert statement is about to be executed -## Best Practices +## Best practices 1. **Consistency**: Follow established patterns throughout the codebase 2. **Readability**: Prioritize code readability and maintainability From 724450fed0e673007ba6d292a048efa715009d43 Mon Sep 17 00:00:00 2001 From: Mark Kincaid Date: Mon, 25 Aug 2025 14:34:42 -0700 Subject: [PATCH 8/8] Minor changes --- docs/contributing/code-style/sql.md | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/docs/contributing/code-style/sql.md b/docs/contributing/code-style/sql.md index 6b8834925..209eb6d5f 100644 --- a/docs/contributing/code-style/sql.md +++ b/docs/contributing/code-style/sql.md @@ -336,11 +336,6 @@ CREATE NONCLUSTERED INDEX [IX_OrganizationUser_UserIdOrganizationIdStatus] - **Schema**: Use `[dbo]` prefix for all objects - **Object names**: Always use square brackets `[dbo].[TableName]` -### User defined types - -- **User Defined Types**: Filenames should be `{TypeName}.sql` (e.g., `GuidIdArray.sql`) - - Note: Use sparingly as they cause downstream maintenance and performance problems - ### Select statements - `SELECT` keyword on its own line @@ -398,7 +393,7 @@ END Note: When adding parameters to an existing stored procedure, a default value must be specified to ensure backward compatibility and ensure existing code can be called without modification. -Also use `SET NOCOUNT ON` to prevent the automatic return of row count messages, which improves +Use `SET NOCOUNT ON` to prevent the automatic return of row count messages, which improves performance and ensures consistent behavior across different client applications that might handle these messages differently.