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
2 changes: 1 addition & 1 deletion e2e/questdb
Submodule questdb updated 51 files
+4 −6 benchmarks/pom.xml
+191 −0 benchmarks/src/main/java/org/questdb/MergeUnionAllBenchmark.java
+2 −3 compat/pom.xml
+1 −1 core/Dockerfile
+6 −7 core/pom.xml
+12 −0 core/src/main/java/io/questdb/cairo/CairoEngine.java
+1 −3 core/src/main/java/io/questdb/cairo/ColumnTypeConverter.java
+16 −13 core/src/main/java/io/questdb/cairo/TableReader.java
+42 −0 core/src/main/java/io/questdb/cairo/mv/ForwardingMatViewStateStore.java
+509 −71 core/src/main/java/io/questdb/cairo/mv/MatViewRefreshJob.java
+14 −2 core/src/main/java/io/questdb/cairo/mv/MatViewRefreshTask.java
+406 −10 core/src/main/java/io/questdb/cairo/mv/MatViewState.java
+22 −0 core/src/main/java/io/questdb/cairo/mv/MatViewStateStore.java
+278 −5 core/src/main/java/io/questdb/cairo/mv/MatViewStateStoreImpl.java
+2 −2 core/src/main/java/io/questdb/cairo/mv/MatViewTimerJob.java
+31 −0 core/src/main/java/io/questdb/cairo/mv/NoOpMatViewStateStore.java
+7 −0 core/src/main/java/io/questdb/cairo/wal/ApplyWal2TableJob.java
+1 −1 core/src/main/java/io/questdb/cairo/wal/WalPurgeJob.java
+171 −17 core/src/main/java/io/questdb/cutlass/qwp/server/QwpIngressProcessorState.java
+38 −16 core/src/main/java/io/questdb/cutlass/qwp/server/QwpIngressUpgradeProcessor.java
+32 −1 core/src/main/java/io/questdb/cutlass/qwp/server/QwpTudCache.java
+40 −0 core/src/main/java/io/questdb/griffin/ExecutionState.java
+154 −14 core/src/main/java/io/questdb/griffin/SqlCodeGenerator.java
+26 −1 core/src/main/java/io/questdb/griffin/SqlCompilerImpl.java
+4 −0 core/src/main/java/io/questdb/griffin/SqlExecutionContext.java
+10 −0 core/src/main/java/io/questdb/griffin/SqlExecutionContextImpl.java
+105 −3 core/src/main/java/io/questdb/griffin/SqlOptimiser.java
+9 −0 core/src/main/java/io/questdb/griffin/engine/QueryProgress.java
+9 −0 core/src/main/java/io/questdb/griffin/engine/union/AbstractSetRecordCursor.java
+5 −1 core/src/main/java/io/questdb/griffin/engine/union/AbstractSetRecordCursorFactory.java
+38 −0 core/src/main/java/io/questdb/griffin/engine/union/MergeUnionAllRecord.java
+245 −0 core/src/main/java/io/questdb/griffin/engine/union/MergeUnionAllRecordCursor.java
+228 −0 core/src/main/java/io/questdb/griffin/engine/union/MergeUnionAllRecordCursorFactory.java
+196 −0 core/src/main/java/io/questdb/griffin/engine/union/MergeUnionAllRecordCursorFactoryBuilder.java
+1 −6 core/src/main/java/io/questdb/griffin/engine/union/UnionAllRecordCursor.java
+6 −0 core/src/main/java/io/questdb/griffin/engine/union/UnionSymbolCastRecordCursorFactory.java
+249 −0 core/src/test/java/io/questdb/test/cairo/TableReaderTest.java
+3,871 −0 core/src/test/java/io/questdb/test/cairo/mv/MatViewPendingInvalidationTrapTest.java
+5 −2 core/src/test/java/io/questdb/test/cairo/o3/O3FailureTest.java
+20 −6 core/src/test/java/io/questdb/test/cutlass/qwp/AbstractQwpBootstrapTest.java
+11 −7 core/src/test/java/io/questdb/test/cutlass/qwp/QwpIngressDemoteRaceFuzzTest.java
+334 −5 core/src/test/java/io/questdb/test/cutlass/qwp/QwpIngressProcessorStateTest.java
+13 −9 core/src/test/java/io/questdb/test/cutlass/qwp/QwpIngressReadOnlyRefusalShapeTest.java
+149 −0 core/src/test/java/io/questdb/test/cutlass/qwp/e2e/QwpAckSeqTxnCoverageBlackBoxTest.java
+113 −0 core/src/test/java/io/questdb/test/cutlass/websocket/QwpIngressAckLeapfrogTest.java
+89 −0 core/src/test/java/io/questdb/test/griffin/AlterTableChangeColumnTypeTest.java
+122 −0 core/src/test/java/io/questdb/test/griffin/ExecutionStateTest.java
+1,134 −0 core/src/test/java/io/questdb/test/griffin/MergeUnionAllTest.java
+64 −31 core/src/test/java/io/questdb/test/griffin/SqlOptimiserTest.java
+3 −4 pom.xml
+4 −5 utils/pom.xml
43 changes: 43 additions & 0 deletions src/utils/questdb/client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import "../../test/stubBrowserGlobals"
import { afterEach, describe, expect, it, vi } from "vitest"
import { Client } from "./client"
import { Type } from "./types"
import { ssoAuthState } from "../../modules/OAuth2/ssoAuthState"
import { AuthPayload } from "../../modules/OAuth2/types"

const response = (body: Record<string, unknown>): Response =>
({
Expand Down Expand Up @@ -62,3 +64,44 @@ describe("Client queryRaw NOTICE timings", () => {
expect(result).not.toHaveProperty("timings")
})
})

describe("Client token refresh", () => {
afterEach(() => {
ssoAuthState.clearAuthPayload()
})

it("does not deadlock when a token refresh fails at the transport level", async () => {
// Given an active SSO session whose token is inside the 30s refresh window
ssoAuthState.setAuthPayload({
access_token: "stale",
refresh_token: "refresh",
expires_at: new Date(new Date().getTime() + 10_000).toString(),
} as AuthPayload)

vi.stubGlobal(
"fetch",
vi.fn().mockResolvedValue(response({ notice: "hint applied" })),
)
const errorSpy = vi.spyOn(console, "error").mockImplementation(() => {})

// And a refresh that rejects, e.g. the token endpoint is unreachable or
// answers with a non-JSON body
const client = new Client()
client.refreshTokenMethod = () => Promise.reject(new Error("network down"))

// When a query runs, it must not hang waiting on a stuck refresh flag: the
// failure is swallowed and the request proceeds with the stale token (the
// server would then answer 401 and drive the normal re-auth flow).
const result = await client.queryRaw("SELECT 1")

expect(result.type).toBe(Type.NOTICE)
expect(fetch).toHaveBeenCalledTimes(1)
expect(errorSpy).toHaveBeenCalled()

// And a subsequent query still goes through — the flag was reset
await client.queryRaw("SELECT 1")
expect(fetch).toHaveBeenCalledTimes(2)

errorSpy.mockRestore()
})
})
50 changes: 31 additions & 19 deletions src/utils/questdb/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,26 +61,38 @@ export class Client {

private refreshAuthToken = async () => {
Client.refreshTokenPending = true
await new Promise((resolve) => {
const interval = setInterval(async () => {
if (Client.numOfPendingQueries === 0) {
clearInterval(interval)
const newToken = await this.refreshTokenMethod()
if (newToken.access_token) {
this.setCommonHeaders({
...this.commonHeaders,
Authorization: `Bearer ${
newToken.groups_encoded_in_token
? newToken.id_token
: newToken.access_token
}`,
})
try {
// Wait until all in-flight queries have finished before swapping the auth
// header, so we don't change it out from under a pending request.
await new Promise<void>((resolve) => {
const interval = setInterval(() => {
if (Client.numOfPendingQueries === 0) {
clearInterval(interval)
resolve()
}
Client.refreshTokenPending = false
return resolve(true)
}
}, 50)
})
}, 50)
})
const newToken = await this.refreshTokenMethod()
if (newToken.access_token) {
this.setCommonHeaders({
...this.commonHeaders,
Authorization: `Bearer ${
newToken.groups_encoded_in_token
? newToken.id_token
: newToken.access_token
}`,
})
}
} catch (error) {
// A transport-level failure (token endpoint unreachable, non-JSON
// response, etc.) must not leave refreshTokenPending stuck true, which
// would deadlock every subsequent query. We keep the stale token in
// place; the next request will get a 401 and drive the normal re-auth
// flow, matching what happens when there is no refresh token at all.
console.error("Failed to refresh the auth token", error)
} finally {
Client.refreshTokenPending = false
}
}

static encodeParams = (
Expand Down
Loading