Skip to content

Commit 61a01a7

Browse files
committed
Add support for ignoring queries that violate an index requirement based on callstack
1 parent e3213f7 commit 61a01a7

File tree

3 files changed

+34
-3
lines changed

3 files changed

+34
-3
lines changed

src/AsyncMysql/AsyncMysqlConnection.php

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ public function __construct(private string $host, private int $port, private str
4343
dict<string, string> $_query_attributes = dict[],
4444
): Awaitable<AsyncMysqlQueryResult> {
4545
Logger::log(Verbosity::QUERIES, "SQLFake [verbose]: $query");
46+
QueryContext::$query = $query;
4647

4748
$config = $this->server->config;
4849
$strict_sql_before = QueryContext::$strictSQLMode;
@@ -62,11 +63,13 @@ public function __construct(private string $host, private int $port, private str
6263
try {
6364
list($results, $rows_affected) = SQLCommandProcessor::execute($query, $this);
6465
} catch (\Exception $e) {
65-
// this makes debugging a failing unit test easier, show the actual query that failed parsing along with the parser error
6666
QueryContext::$strictSQLMode = $strict_sql_before;
6767
QueryContext::$strictSchemaMode = $strict_schema_before;
68+
// Make debugging a failing unit test locally easier,
69+
// by showing the actual query that failed parsing along with the parser error
6870
$msg = $e->getMessage();
6971
$type = \get_class($e);
72+
Logger::log(Verbosity::QUIET, $e->getFile().' '.$e->getLine());
7073
Logger::log(Verbosity::QUIET, "SQL Fake $type: $msg in SQL query: $query");
7174
throw $e;
7275
}
@@ -82,7 +85,10 @@ public function __construct(private string $host, private int $port, private str
8285
}
8386

8487
<<__Override>>
85-
public function queryf(\HH\FormatString<\HH\SQLFormatter> $query, mixed ...$args): Awaitable<AsyncMysqlQueryResult> {
88+
public function queryf(
89+
\HH\FormatString<\HH\SQLFormatter> $query,
90+
mixed ...$args
91+
): Awaitable<AsyncMysqlQueryResult> {
8692
invariant($query is string, '\\HH\\FormatString<_> is opaque, but we need it to be a string here.');
8793
return $this->query($this->queryStringifier->formatString($query, vec($args)));
8894
}

src/Query/QueryPlanner.hack

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,26 @@ abstract class QueryPlanner {
161161
false,
162162
);
163163
} else if (QueryContext::$requireIndexes) {
164-
throw new \Exception('Query without index: '.(QueryContext::$query ?? ''));
164+
$stack = \debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS);
165+
C\pop_front(inout $stack);
166+
167+
$grandfathered = false;
168+
foreach ($stack as $stack_frame) {
169+
$class = $stack_frame['class'] ?? null;
170+
$function = $stack_frame['function'];
171+
172+
foreach (QueryContext::$allowed_index_violation_traces as $allowed_trace) {
173+
$allowed_trace_class = $allowed_trace['class'] ?? null;
174+
175+
if ($class === $allowed_trace_class && $allowed_trace['function'] === $function) {
176+
$grandfathered = true;
177+
}
178+
}
179+
}
180+
181+
if (!$grandfathered) {
182+
throw new \Exception('Query without index: '.(QueryContext::$query ?? ''));
183+
}
165184
}
166185

167186
return null;

src/QueryContext.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,12 @@
2929
*/
3030
public static bool $requireIndexes = false;
3131

32+
/**
33+
* Allow violations of the must-use-index policy when the stack trace matches any of these
34+
* class-function pairs.
35+
*/
36+
public static vec<shape(?'class' => string, 'function' => string)> $allowed_index_violation_traces = vec[];
37+
3238
/**
3339
* Require the presence of the table's Vitess sharding key
3440
*/

0 commit comments

Comments
 (0)