Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 66 additions & 0 deletions src/sql/recent-query.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,30 @@ test("isSelectQuery returns false for non-SELECT statements", () => {
).toBe(false);
});

test("isSelectQuery returns false for DML containing SELECT subqueries", () => {
expect(
RecentQuery.isSelectQuery(
makeRawQuery({
query: 'UPDATE "public"."oban_jobs" SET "state" = $1 FROM (SELECT id FROM t) s WHERE id = s.id',
}),
),
).toBe(false);
expect(
RecentQuery.isSelectQuery(
makeRawQuery({
query: "INSERT INTO archive SELECT * FROM users",
}),
),
).toBe(false);
expect(
RecentQuery.isSelectQuery(
makeRawQuery({
query: "DELETE FROM users WHERE EXISTS (SELECT 1 FROM banned)",
}),
),
).toBe(false);
});

// --- isSystemQuery ---

test("isSystemQuery returns true for pg_ tables", () => {
Expand Down Expand Up @@ -193,3 +217,45 @@ test("analyze throws on unparseable SQL", async () => {
RecentQuery.analyze(data, testHash, 3000),
).rejects.toThrow();
});

// --- statementType-based isSelectQuery via analyze ---

test("analyze sets isSelectQuery=true for SELECT", async () => {
const data = makeRawQuery({ query: "SELECT * FROM users" });
const rq = await RecentQuery.analyze(data, testHash, 1000);
expect(rq.isSelectQuery).toBe(true);
});

test("analyze sets isSelectQuery=true for CTE with SELECT", async () => {
const data = makeRawQuery({
query: "WITH cte AS (SELECT id FROM users) SELECT * FROM cte",
});
const rq = await RecentQuery.analyze(data, testHash, 1000);
expect(rq.isSelectQuery).toBe(true);
});

test("analyze sets isSelectQuery=false for UPDATE even with SELECT subquery", async () => {
const data = makeRawQuery({
query:
'UPDATE "public"."jobs" SET "state" = $1 FROM (SELECT id FROM "public"."jobs" WHERE state = $2 LIMIT 10) AS s1 WHERE "jobs".id = s1.id',
});
const rq = await RecentQuery.analyze(data, testHash, 1000);
expect(rq.isSelectQuery).toBe(false);
});

test("analyze sets isSelectQuery=false for INSERT ... SELECT", async () => {
const data = makeRawQuery({
query: "INSERT INTO archive SELECT * FROM users WHERE active = false",
});
const rq = await RecentQuery.analyze(data, testHash, 1000);
expect(rq.isSelectQuery).toBe(false);
});

test("analyze sets isSelectQuery=false for DELETE with EXISTS subquery", async () => {
const data = makeRawQuery({
query:
"DELETE FROM users WHERE EXISTS (SELECT 1 FROM banned WHERE banned.user_id = users.id)",
});
const rq = await RecentQuery.analyze(data, testHash, 1000);
expect(rq.isSelectQuery).toBe(false);
});
10 changes: 8 additions & 2 deletions src/sql/recent-query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
PostgresQueryBuilder,
PssRewriter,
SQLCommenterTag,
type StatementType,

Check failure on line 11 in src/sql/recent-query.ts

View workflow job for this annotation

GitHub Actions / release

Module '"@query-doctor/core"' has no exported member 'StatementType'.
type TableReference,
} from "@query-doctor/core";
import { parse } from "@libpg-query/parser";
Expand Down Expand Up @@ -46,6 +47,7 @@
readonly hash: QueryHash,
readonly seenAt: number,
analysisSkipped = false,
statementType?: StatementType,
) {
this.username = data.username;
this.query = data.query;
Expand All @@ -57,7 +59,9 @@
this.analysisSkipped = analysisSkipped;

this.isSystemQuery = RecentQuery.isSystemQuery(tableReferences);
this.isSelectQuery = RecentQuery.isSelectQuery(data);
this.isSelectQuery = statementType !== undefined
? statementType === "select"
: RecentQuery.isSelectQuery(data);
this.isIntrospection = RecentQuery.isIntrospection(data);
this.isTargetlessSelectQuery = this.isSelectQuery
? RecentQuery.isTargetlessSelectQuery(tableReferences)
Expand Down Expand Up @@ -123,6 +127,8 @@
analysis.nudges,
hash,
seenAt,
false,
analysis.statementType,

Check failure on line 131 in src/sql/recent-query.ts

View workflow job for this annotation

GitHub Actions / release

Property 'statementType' does not exist on type 'AnalysisResult'.
);
}

Expand All @@ -147,7 +153,7 @@
}

static isSelectQuery(data: RawRecentQuery): boolean {
return /select/i.test(data.query);
return /^\s*select/i.test(data.query);
}

static isSystemQuery(referencedTables: TableReference[]): boolean {
Expand Down
Loading