Commit 9fded38
committed
Per-table INDEX-hint existence validation —
Msg 307 (unknown INDEX(N) id) + Msg 308 (unknown INDEX(name) / INDEX = name) ship verbatim. Both fire only on FROM-source / JOIN-RHS heap-table positions — DML targets already short-circuit on Msg 1069 first (ValidateDmlTargetHints runs before per-index validation, matching probe: UPDATE t WITH (INDEX(name)) raises 1069 not 308 on real SQL Server 2025). TableHintInfo gains a nullable List<IndexHintArgument> IndexArguments field and a companion IndexHintArgument readonly struct (Id? xor Name?); the INDEX-name case in ConsumeOneTableHint now dispatches to a dedicated ConsumeIndexHintArguments walker rather than SkipBalancedParens. The walker accepts both INDEX(arg [, …]) and INDEX = arg forms, captures each numeric-literal arg as IndexHintArgument.ForId(value.AsInt32) and each identifier (Name-token) arg as IndexHintArgument.ForName(source.ToString()), and surfaces negative-integer / other-shape inputs as generic Msg 102 (probe-confirmed: real SQL Server rejects INDEX(-1) the same way). FORCESEEK / FORCESCAN keep the existing parse-and-discard posture via the SkipBalancedParens fall-through. New ValidateIndexHintArguments(TableHintInfo, HeapTable, string qualifiedTableName) walks the captured args and raises on the first failure: integer form valid if id == 0 (the "heap scan" reference, accepted even on clustered tables — probe-confirmed INDEX(0) works on every shape) or 1 <= id <= KeyConstraints.Count + Indexes.Count (matches sys.indexes row-count for the table excluding the synthetic HEAP row); name form valid if matches any KeyConstraints[].Name (PRIMARY KEY / UNIQUE) or Indexes[].Name case-insensitively via Collation.Default.Equals. Wired at the heap-table FROM-source path in Selection.cs:1052 (immediately after ParseOptionalTableHints returns); the call site builds the qualifier as $"{objectName.ImmediateQualifier ?? Database.DefaultSchemaName}.{heapTable.Name}" so 2-part au.t references surface as 'au.t' in the message and 1-part t surfaces as 'dbo.t'. New error factories IndexHintIdNotFound(int, string) and IndexHintNameNotFound(string, string) in SimulatedSqlException.ConcurrencyErrors.cs carry the probe-confirmed verbatim wording (single-quote convention, (specified in the FROM clause) suffix hard-coded even on JOIN-RHS per real-server behavior). 13 new QueryHintTests cover: known/unknown named-arg pairs, PRIMARY KEY constraint name, UNIQUE constraint name, case-insensitive name match, =-form known/unknown name, out-of-range int on PK-table (Msg 307), INDEX(1) on heap-only table (Msg 307), INDEX(0) always-valid, multi-arg INDEX(bad, good) raising 308 on first failing arg, schema-qualified table name embedded in error, negative-int rejected as Msg 102 at parse. The previously-passing Select_IndexHint_NamedArg_ReturnsRows test (parse-and-ignore stance) becomes Select_IndexHint_UnknownNamedArg_RaisesMsg308. CLAUDE.md "Query-hint gaps" section rewritten: the "hint-conflict and DML-target-specific rejections aren't enforced" claim removed (Msg 1047 / 1065 / 1069 were already shipped — doc was stale); replaced with explicit "Hint-conflict + DML-target rejections ship verbatim" and "Per-table index-hint validation ships" paragraphs documenting Msg 307 / 308 with their validation rules. Remaining hint-surface gaps now narrow to the FROM-source (unknown) Msg 102-vs-207/321 divergence and the FORCESEEK(name(cols)) nested-form name validation. docs/claude/query-hints.md "Not enforced" section split into "Enforced rejections" (with detailed Msg 307 / 308 / 1047 / 1065 / 1069 wording + rule docs) and a shrunken "Not enforced" (FORCESEEK nested-form name validation + Msg 8622 plan rejection + INDEX = (value-list) equals-form); probe-artifacts list at the doc tail updated with the new Msg 307 / 308 / INDEX(0) / INDEX(-1) findings.1 parent e2b1ca1 commit 9fded38
6 files changed
Lines changed: 356 additions & 27 deletions
File tree
- SqlServerSimulator.Tests
- SqlServerSimulator
- Errors
- Parser
- docs/claude
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
181 | 181 | | |
182 | 182 | | |
183 | 183 | | |
184 | | - | |
| 184 | + | |
185 | 185 | | |
186 | 186 | | |
187 | 187 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
117 | 117 | | |
118 | 118 | | |
119 | 119 | | |
120 | | - | |
121 | | - | |
122 | | - | |
123 | | - | |
124 | | - | |
125 | 120 | | |
126 | | - | |
| 121 | + | |
127 | 122 | | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
128 | 132 | | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
129 | 141 | | |
130 | | - | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
131 | 151 | | |
132 | 152 | | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
| 211 | + | |
| 212 | + | |
| 213 | + | |
| 214 | + | |
| 215 | + | |
| 216 | + | |
| 217 | + | |
| 218 | + | |
| 219 | + | |
| 220 | + | |
| 221 | + | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
| 230 | + | |
| 231 | + | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
| 236 | + | |
| 237 | + | |
| 238 | + | |
133 | 239 | | |
134 | 240 | | |
135 | 241 | | |
| |||
Lines changed: 24 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
55 | 55 | | |
56 | 56 | | |
57 | 57 | | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
58 | 82 | | |
59 | 83 | | |
60 | 84 | | |
| |||
0 commit comments