Skip to content

Commit 3046d42

Browse files
Add internal dev prompts and update style guide
Adds new internal documentation for pipeline output, SMO vs T-SQL usage, and SQL Server version support in .github/prompts. Updates style.md with test management guidelines and checklist. Refactors CLAUDE.md to reference new prompt files and streamline guidance on SQL version support, SMO vs T-SQL, and pipeline output.
1 parent a0c677b commit 3046d42

5 files changed

Lines changed: 651 additions & 1034 deletions

File tree

.github/prompts/pipeline-output.md

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
# Pipeline Output Patterns
2+
3+
**CRITICAL RULE**: NEVER collect objects in an ArrayList or array and output them at the end. Output objects to the pipeline immediately as they are created.
4+
5+
## Why Immediate Output
6+
7+
- **Memory Efficiency**: Objects are released to the pipeline immediately, not held in memory
8+
- **User Experience**: Users see results streaming in real-time, not waiting until the end
9+
- **Pipeline Compatibility**: Enables proper pipeline chaining and early termination (Ctrl+C)
10+
- **Error Resilience**: Partial results are available even if the command fails partway through
11+
12+
## Correct Pattern - Output Immediately
13+
14+
```powershell
15+
foreach ($instance in $SqlInstance) {
16+
$server = Connect-DbaInstance -SqlInstance $instance -SqlCredential $SqlCredential
17+
18+
foreach ($db in $server.Databases) {
19+
# Output each object immediately to the pipeline
20+
[PSCustomObject]@{
21+
ComputerName = $server.ComputerName
22+
InstanceName = $server.ServiceName
23+
SqlInstance = $server.DomainInstanceName
24+
Database = $db.Name
25+
Size = $db.Size
26+
}
27+
}
28+
}
29+
```
30+
31+
## Wrong Patterns - DO NOT USE
32+
33+
### ArrayList Collection (OLD PATTERN)
34+
35+
```powershell
36+
# WRONG - This is an outdated anti-pattern
37+
$results = New-Object System.Collections.ArrayList
38+
39+
foreach ($instance in $SqlInstance) {
40+
$server = Connect-DbaInstance -SqlInstance $instance -SqlCredential $SqlCredential
41+
42+
foreach ($db in $server.Databases) {
43+
$null = $results.Add([PSCustomObject]@{
44+
ComputerName = $server.ComputerName
45+
Database = $db.Name
46+
})
47+
}
48+
}
49+
50+
# WRONG - Holding everything until the end
51+
$results
52+
```
53+
54+
### Array Concatenation (Worst Performance)
55+
56+
```powershell
57+
# WRONG - Array concatenation is extremely slow
58+
$results = @()
59+
60+
foreach ($db in $databases) {
61+
$results += [PSCustomObject]@{
62+
Name = $db.Name
63+
}
64+
}
65+
66+
$results
67+
```
68+
69+
## No -Detailed or -Simple Parameters
70+
71+
Do NOT create `-Detailed` or `-Simple` switch parameters that change the output object structure. This is an outdated pattern that creates confusion and breaks pipeline expectations.
72+
73+
```powershell
74+
# WRONG - Do not create output mode switches
75+
param(
76+
[switch]$Detailed,
77+
[switch]$Simple
78+
)
79+
80+
if ($Detailed) {
81+
# Return more properties
82+
} elseif ($Simple) {
83+
# Return fewer properties
84+
}
85+
```
86+
87+
Instead, return a consistent object with all relevant properties. Users can select the properties they want with `Select-Object`.
88+
89+
## Process Block Pattern
90+
91+
When using `ValueFromPipeline`, output in the `process` block, not `end`:
92+
93+
```powershell
94+
function Get-DbaExample {
95+
[CmdletBinding()]
96+
param (
97+
[Parameter(ValueFromPipeline)]
98+
[DbaInstanceParameter[]]$SqlInstance,
99+
[PSCredential]$SqlCredential
100+
)
101+
102+
process {
103+
foreach ($instance in $SqlInstance) {
104+
$server = Connect-DbaInstance -SqlInstance $instance -SqlCredential $SqlCredential
105+
106+
foreach ($item in $server.SomeCollection) {
107+
# Output immediately in process block
108+
[PSCustomObject]@{
109+
ComputerName = $server.ComputerName
110+
InstanceName = $server.ServiceName
111+
SqlInstance = $server.DomainInstanceName
112+
Name = $item.Name
113+
}
114+
}
115+
}
116+
}
117+
}
118+
```
119+
120+
## Summary
121+
122+
- Output objects immediately as they are created
123+
- Never use ArrayList, Generic.List, or array += to collect results
124+
- Never use `-Detailed`/`-Simple` output mode switches
125+
- Process pipeline input in the `process` block, not `end`
126+
- Let PowerShell's pipeline handle the collection if the user needs it

.github/prompts/smo-vs-tsql.md

Lines changed: 246 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,246 @@
1+
# SMO vs T-SQL Usage Guide
2+
3+
**GUIDING PRINCIPLE**: Default to using SMO (SQL Server Management Objects) first. Only use T-SQL when SMO doesn't provide the functionality or when T-SQL offers better performance or user experience.
4+
5+
## Why SMO First
6+
7+
- **Abstraction**: SMO provides object-oriented interface that handles version differences automatically
8+
- **Type Safety**: Strong typing reduces errors compared to dynamic T-SQL strings
9+
- **Built-in Methods**: Common operations (Create, Drop, Alter, Script) are provided out-of-the-box
10+
- **Consistency**: SMO ensures consistent behavior across SQL Server versions
11+
- **Less Code**: Often requires fewer lines than equivalent T-SQL
12+
13+
## When to Use SMO
14+
15+
### 1. Object Manipulation
16+
Creating, dropping, altering database objects:
17+
18+
```powershell
19+
# PREFERRED - SMO for object manipulation
20+
$newdb = New-Object Microsoft.SqlServer.Management.Smo.Database($server, $dbName)
21+
$newdb.Collation = $Collation
22+
$newdb.RecoveryModel = $RecoveryModel
23+
$newdb.Create()
24+
25+
# Dropping objects
26+
$destServer.Roles[$roleName].Drop()
27+
$destServer.Roles.Refresh()
28+
```
29+
30+
### 2. Object Scripting
31+
Generating T-SQL from existing objects:
32+
33+
```powershell
34+
# PREFERRED - SMO scripting with execution via Query
35+
$sql = $currentRole.Script() | Out-String
36+
Write-Message -Level Debug -Message $sql
37+
$destServer.Query($sql)
38+
39+
# Another example
40+
$destServer.Query($currentEndpoint.Script()) | Out-Null
41+
```
42+
43+
### 3. Object Enumeration
44+
Accessing collections and properties:
45+
46+
```powershell
47+
# PREFERRED - SMO for object access
48+
$databases = $server.Databases
49+
$database = $server.Databases[$dbName]
50+
$isSystemDb = $database.IsSystemObject
51+
$members = $currentRole.EnumMemberNames()
52+
```
53+
54+
### 4. Object Properties
55+
Reading and setting object attributes:
56+
57+
```powershell
58+
# PREFERRED - SMO for property access
59+
$recoveryModel = $db.RecoveryModel
60+
$owner = $db.Owner
61+
$lastBackup = $db.LastBackupDate
62+
$size = $db.Size
63+
```
64+
65+
## When T-SQL is Appropriate
66+
67+
### 1. System Views and DMVs
68+
When SMO doesn't expose the data efficiently:
69+
70+
```powershell
71+
# T-SQL for system catalog queries
72+
$sql = @"
73+
SELECT
74+
p.name AS ProcedureName,
75+
SCHEMA_NAME(p.schema_id) AS SchemaName,
76+
p.object_id,
77+
m.definition AS DllPath
78+
FROM sys.procedures p
79+
INNER JOIN sys.all_objects o ON p.object_id = o.object_id
80+
LEFT JOIN sys.sql_modules m ON p.object_id = m.object_id
81+
WHERE p.type = 'X'
82+
AND p.is_ms_shipped = 0
83+
ORDER BY p.name
84+
"@
85+
$sourceXPs = $sourceServer.Query($sql)
86+
```
87+
88+
### 2. Performance-Critical Queries
89+
When retrieving large result sets:
90+
91+
```powershell
92+
# T-SQL for efficient data retrieval
93+
$querylastused = "SELECT dbname, max(last_read) last_read FROM sys.dm_db_index_usage_stats GROUP BY dbname"
94+
$dblastused = $server.Query($querylastused)
95+
```
96+
97+
### 3. Version-Specific Logic
98+
Different queries for different SQL Server versions:
99+
100+
```powershell
101+
# T-SQL when version-specific system tables/views are needed
102+
if ($server.VersionMajor -eq 8) {
103+
# SQL Server 2000 uses system tables
104+
$backed_info = $server.Query("SELECT name, SUSER_SNAME(sid) AS [Owner] FROM master.dbo.sysdatabases")
105+
} else {
106+
# SQL Server 2005+ uses catalog views
107+
$backed_info = $server.Query("SELECT name, SUSER_SNAME(owner_sid) AS [Owner] FROM sys.databases")
108+
}
109+
```
110+
111+
### 4. System Stored Procedures
112+
When the operation requires a specific system proc:
113+
114+
```powershell
115+
# T-SQL for system procedures that have no SMO equivalent
116+
$dropSql = "EXEC sp_dropextendedproc @functname = N'$xpFullName'"
117+
$null = $destServer.Query($dropSql)
118+
119+
$createSql = "EXEC sp_addextendedproc @functname = N'$xpFullName', @dllname = N'$destDllPath'"
120+
$null = $destServer.Query($createSql)
121+
```
122+
123+
### 5. User-Friendly Features
124+
When T-SQL makes the command more intuitive:
125+
126+
```powershell
127+
# Sometimes T-SQL provides better UX than SMO
128+
# Example: Parameterized queries for filtering
129+
$splatQuery = @{
130+
SqlInstance = $instance
131+
Query = "SELECT * FROM users WHERE Givenname = @name"
132+
SqlParameter = @{ Name = "Maria" }
133+
}
134+
$result = Invoke-DbaQuery @splatQuery
135+
```
136+
137+
## Hybrid Pattern (Most Common)
138+
139+
Most dbatools commands use both SMO and T-SQL strategically:
140+
141+
```powershell
142+
# Get SMO server object
143+
$sourceServer = Connect-DbaInstance -SqlInstance $Source -SqlCredential $SourceSqlCredential
144+
145+
# Use SMO for object enumeration
146+
$sourceRoles = $sourceServer.Roles | Where-Object IsFixedRole -eq $false
147+
148+
# Use T-SQL for complex permission queries
149+
$splatPermissions = @{
150+
SqlInstance = $sourceServer
151+
IncludeServerLevel = $true
152+
}
153+
$sourcePermissions = Get-DbaPermission @splatPermissions | Where-Object Grantee -eq $roleName
154+
155+
# Use SMO for object manipulation
156+
foreach ($currentRole in $sourceRoles) {
157+
# Script the object using SMO
158+
$sql = $currentRole.Script() | Out-String
159+
160+
# Execute via T-SQL
161+
$destServer.Query($sql)
162+
163+
# Use SMO methods for membership
164+
$members = $currentRole.EnumMemberNames()
165+
foreach ($member in $members) {
166+
$destServer.Roles[$roleName].AddMember($member)
167+
}
168+
}
169+
```
170+
171+
## Decision Tree
172+
173+
1. **Does SMO expose the functionality cleanly?**
174+
- YES → Use SMO
175+
- NO → Continue to #2
176+
177+
2. **Is this a data retrieval operation from system views/DMVs?**
178+
- YES → Use T-SQL via `$server.Query()`
179+
- NO → Continue to #3
180+
181+
3. **Does the operation require a system stored procedure?**
182+
- YES → Use T-SQL via `$server.Query()`
183+
- NO → Continue to #4
184+
185+
4. **Would T-SQL significantly improve user experience?**
186+
- YES → Use T-SQL (document why in comments)
187+
- NO → Use SMO
188+
189+
## Common Patterns
190+
191+
```powershell
192+
# Pattern 1: SMO object with T-SQL execution of Script()
193+
$sql = $smoObject.Script() | Out-String
194+
$destServer.Query($sql)
195+
196+
# Pattern 2: T-SQL for discovery, SMO for manipulation
197+
$objects = $server.Query("SELECT name FROM sys.objects WHERE type = 'U'")
198+
foreach ($obj in $objects) {
199+
$table = $server.Databases[$dbName].Tables[$obj.name]
200+
$table.Drop() # SMO method
201+
}
202+
203+
# Pattern 3: SMO with T-SQL fallback
204+
try {
205+
$database = $server.Databases[$dbName] # SMO
206+
} catch {
207+
# Fallback to T-SQL if SMO fails
208+
$result = $server.Query("SELECT name FROM sys.databases WHERE name = '$dbName'")
209+
}
210+
```
211+
212+
## Anti-Patterns to Avoid
213+
214+
```powershell
215+
# WRONG - Using T-SQL when SMO provides the functionality
216+
$result = $server.Query("ALTER DATABASE [$dbName] SET RECOVERY FULL")
217+
218+
# CORRECT - Use SMO
219+
$db = $server.Databases[$dbName]
220+
$db.RecoveryModel = "Full"
221+
$db.Alter()
222+
223+
# WRONG - Using T-SQL for object enumeration
224+
$databases = $server.Query("SELECT name FROM sys.databases")
225+
226+
# CORRECT - Use SMO
227+
$databases = $server.Databases
228+
229+
# WRONG - Concatenating T-SQL strings without parameters (SQL injection risk)
230+
$result = $server.Query("SELECT * FROM users WHERE name = '$userName'")
231+
232+
# CORRECT - Use parameterized queries
233+
$splatQuery = @{
234+
Query = "SELECT * FROM users WHERE name = @userName"
235+
SqlParameter = @{ userName = $userName }
236+
}
237+
$result = Invoke-DbaQuery @splatQuery -SqlInstance $server
238+
```
239+
240+
## Summary
241+
242+
- **Default to SMO** for object-oriented operations (Create, Drop, Alter, Script, property access)
243+
- **Use T-SQL** for system views, DMVs, complex queries, system stored procedures, and version-specific logic
244+
- **Combine both** in a hybrid approach when it provides the best balance of functionality and usability
245+
- **Always prefer parameterized queries** when using T-SQL with dynamic values
246+
- **Document your choice** when T-SQL is used instead of SMO for non-obvious reasons

0 commit comments

Comments
 (0)