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: 2 additions & 0 deletions src/formatter/phrases.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,8 @@ const alterActionPhrases: Phrase[] = action(
["Drop", "Partition", "List"],
["Set"],
["Squash"],
["Force", "Drop", "Partition", "List"],
["Force", "Reindex", "Parquet"],
["Dedup"],
["Resume", "Wal"],
["Suspend", "Wal"],
Expand Down
13 changes: 13 additions & 0 deletions src/parser/ast.ts
Original file line number Diff line number Diff line change
Expand Up @@ -554,6 +554,8 @@ export type AlterTableAction =
| AttachPartitionAction
| DetachPartitionAction
| SquashPartitionsAction
| ForceDropPartitionAction
| ForceReindexParquetAction
| SetParamAction
| SetTtlAction
| SetTableFormatAction
Expand Down Expand Up @@ -628,6 +630,17 @@ export interface SquashPartitionsAction {
actionType: "squashPartitions"
}

export interface ForceDropPartitionAction {
actionType: "forceDropPartition"
partitions: string[]
}

export interface ForceReindexParquetAction {
actionType: "forceReindexParquet"
column?: string
partitions?: string[]
}

export interface SetParamAction {
actionType: "setParam"
params: TableParam[]
Expand Down
20 changes: 20 additions & 0 deletions src/parser/cst-types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1538,6 +1538,7 @@ export type AlterTableActionCstChildren = {
Detach?: IToken[];
Squash?: IToken[];
Partitions?: IToken[];
alterTableForceAction?: AlterTableForceActionCstNode[];
Param?: IToken[];
tableParam?: (TableParamCstNode)[];
Ttl?: IToken[];
Expand Down Expand Up @@ -1566,6 +1567,24 @@ export type AlterTableActionCstChildren = {
convertPartitionTarget?: ConvertPartitionTargetCstNode[];
};

export interface AlterTableForceActionCstNode extends CstNode {
name: "alterTableForceAction";
children: AlterTableForceActionCstChildren;
}

export type AlterTableForceActionCstChildren = {
Force: IToken[];
Drop?: IToken[];
Partition?: (IToken)[];
List?: (IToken)[];
StringLiteral?: (IToken)[];
Comma?: (IToken)[];
Reindex?: IToken[];
Parquet?: IToken[];
Column?: IToken[];
identifier?: IdentifierCstNode[];
};

export interface ConvertPartitionTargetCstNode extends CstNode {
name: "convertPartitionTarget";
children: ConvertPartitionTargetCstChildren;
Expand Down Expand Up @@ -3051,6 +3070,7 @@ export interface ICstNodeVisitor<IN, OUT> extends ICstVisitor<IN, OUT> {
memoryLimit(children: MemoryLimitCstChildren, param?: IN): OUT;
alterTableStatement(children: AlterTableStatementCstChildren, param?: IN): OUT;
alterTableAction(children: AlterTableActionCstChildren, param?: IN): OUT;
alterTableForceAction(children: AlterTableForceActionCstChildren, param?: IN): OUT;
convertPartitionTarget(children: ConvertPartitionTargetCstChildren, param?: IN): OUT;
alterMaterializedViewStatement(children: AlterMaterializedViewStatementCstChildren, param?: IN): OUT;
alterMaterializedViewAction(children: AlterMaterializedViewActionCstChildren, param?: IN): OUT;
Expand Down
41 changes: 41 additions & 0 deletions src/parser/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2926,6 +2926,8 @@ class QuestDBParser extends CstParser {
this.CONSUME(Partitions)
},
},
// FORCE (DROP PARTITION LIST ... | REINDEX PARQUET ...)
{ ALT: () => this.SUBRULE(this.alterTableForceAction) },
// SET (PARAM ... | TTL ... | TYPE WAL ... | STORAGE POLICY ...)
{
ALT: () => {
Expand Down Expand Up @@ -3066,6 +3068,45 @@ class QuestDBParser extends CstParser {
])
})

// FORCE DROP PARTITION LIST '<p>' [, ...]
// FORCE REINDEX PARQUET [COLUMN <col>] [PARTITION LIST '<p>' [, ...]]
private alterTableForceAction = this.RULE("alterTableForceAction", () => {
this.CONSUME(Force)
this.OR([
{
ALT: () => {
this.CONSUME(Drop)
this.CONSUME(Partition)
this.CONSUME(List)
this.CONSUME(StringLiteral)
this.MANY(() => {
this.CONSUME(Comma)
this.CONSUME1(StringLiteral)
})
},
},
{
ALT: () => {
this.CONSUME(Reindex)
this.CONSUME(Parquet)
this.OPTION(() => {
this.CONSUME(Column)
this.SUBRULE(this.identifier)
})
this.OPTION1(() => {
this.CONSUME1(Partition)
this.CONSUME1(List)
this.CONSUME2(StringLiteral)
this.MANY1(() => {
this.CONSUME1(Comma)
this.CONSUME3(StringLiteral)
})
})
},
},
])
})

private convertPartitionTarget = this.RULE("convertPartitionTarget", () => {
// Optional LIST before TO
this.OPTION(() => {
Expand Down
15 changes: 15 additions & 0 deletions src/parser/toSql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -964,6 +964,21 @@ function alterTableToSql(stmt: AST.AlterTableStatement): string {
case "squashPartitions":
parts.push("SQUASH PARTITIONS")
break
case "forceDropPartition":
parts.push("FORCE DROP PARTITION LIST")
parts.push(action.partitions.map((p) => escapeString(p)).join(", "))
break
case "forceReindexParquet":
parts.push("FORCE REINDEX PARQUET")
if (action.column) {
parts.push("COLUMN")
parts.push(escapeIdentifier(action.column))
}
if (action.partitions && action.partitions.length > 0) {
parts.push("PARTITION LIST")
parts.push(action.partitions.map((p) => escapeString(p)).join(", "))
}
break
case "setParam":
parts.push("SET PARAM")
parts.push(
Expand Down
26 changes: 26 additions & 0 deletions src/parser/visitor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import type {
AlterServiceAccountStatementCstChildren,
AlterStatementCstChildren,
AlterTableActionCstChildren,
AlterTableForceActionCstChildren,
AlterTableStatementCstChildren,
AlterUserActionCstChildren,
AlterUserStatementCstChildren,
Expand Down Expand Up @@ -2374,7 +2375,32 @@ class QuestDBVisitor extends BaseVisitor {
return ctx.NumberLiteral![0].image + (ctx.Identifier?.[0]?.image ?? "")
}

alterTableForceAction(
ctx: AlterTableForceActionCstChildren,
): AST.ForceDropPartitionAction | AST.ForceReindexParquetAction {
const partitions = (ctx.StringLiteral ?? []).map((s: IToken) =>
s.image.slice(1, -1),
)
if (ctx.Drop) {
return { actionType: "forceDropPartition", partitions }
}
const result: AST.ForceReindexParquetAction = {
actionType: "forceReindexParquet",
}
if (ctx.identifier) {
result.column = this.extractIdentifierName(ctx.identifier[0].children)
}
if (partitions.length > 0) {
result.partitions = partitions
}
return result
}

alterTableAction(ctx: AlterTableActionCstChildren): AST.AlterTableAction {
// FORCE DROP PARTITION / FORCE REINDEX PARQUET
if (ctx.alterTableForceAction) {
return this.visit(ctx.alterTableForceAction) as AST.AlterTableAction
}
// SET STORAGE POLICY(...) — must come before generic SET branches below
if (ctx.Set && ctx.storagePolicy) {
return {
Expand Down
9 changes: 9 additions & 0 deletions tests/fixtures/docs-queries.json
Original file line number Diff line number Diff line change
Expand Up @@ -7978,5 +7978,14 @@
},
{
"query": "SELECT avg(b.bid) AS avg_bid, avg(a.ask) AS avg_ask FROM trades AS t HORIZON JOIN bids AS b ON (t.sym = b.sym) HORIZON JOIN asks AS a ON (t.sym = a.sym) RANGE FROM -1s TO 1s STEP 1s AS h"
},
{
"query": "ALTER TABLE trades FORCE REINDEX PARQUET"
},
{
"query": "ALTER TABLE trades FORCE REINDEX PARQUET COLUMN symbol"
},
{
"query": "ALTER TABLE trades FORCE REINDEX PARQUET COLUMN symbol PARTITION LIST '2026-03-01', '2026-03-02'"
}
]
17 changes: 17 additions & 0 deletions tests/formatter/fixtures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,23 @@ export const fixtures: Fixture[] = [
input: "ALTER TABLE t SUSPEND WAL",
expected: ["ALTER TABLE t", "SUSPEND WAL"].join("\n"),
},
{
name: "ALTER TABLE FORCE REINDEX PARQUET starts a line",
input:
"ALTER TABLE trades FORCE REINDEX PARQUET COLUMN symbol PARTITION LIST '2026-03-01'",
expected: [
"ALTER TABLE trades",
"FORCE REINDEX PARQUET COLUMN symbol PARTITION LIST '2026-03-01'",
].join("\n"),
},
{
name: "ALTER TABLE FORCE DROP PARTITION starts a line",
input: "ALTER TABLE trades FORCE DROP PARTITION LIST '2026-03-01'",
expected: [
"ALTER TABLE trades",
"FORCE DROP PARTITION LIST '2026-03-01'",
].join("\n"),
},
{
name: "ALTER TABLE DROP STORAGE POLICY starts a line",
input: "ALTER TABLE t DROP STORAGE POLICY",
Expand Down
73 changes: 73 additions & 0 deletions tests/parser.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7973,6 +7973,60 @@ orders PIVOT (sum(amount) FOR status IN ('open'))`
})
})

describe("FORCE REINDEX PARQUET / FORCE DROP PARTITION — parse & AST", () => {
it("bare FORCE REINDEX PARQUET", () => {
const r = parseToAst("ALTER TABLE t FORCE REINDEX PARQUET")
expect(r.errors).toHaveLength(0)
const s = r.ast[0] as AST.AlterTableStatement
expect(s.action).toEqual({ actionType: "forceReindexParquet" })
})
it("FORCE REINDEX PARQUET with COLUMN and PARTITION LIST", () => {
const r = parseToAst(
"ALTER TABLE t FORCE REINDEX PARQUET COLUMN b PARTITION LIST '2024-01-01', '2024-01-02'",
)
expect(r.errors).toHaveLength(0)
const s = r.ast[0] as AST.AlterTableStatement
expect(s.action).toEqual({
actionType: "forceReindexParquet",
column: "b",
partitions: ["2024-01-01", "2024-01-02"],
})
})
it("FORCE REINDEX PARQUET with PARTITION LIST only", () => {
const r = parseToAst(
"ALTER TABLE t FORCE REINDEX PARQUET PARTITION LIST '2024-01-01'",
)
expect(r.errors).toHaveLength(0)
const s = r.ast[0] as AST.AlterTableStatement
expect(s.action).toEqual({
actionType: "forceReindexParquet",
partitions: ["2024-01-01"],
})
})
it("FORCE DROP PARTITION LIST", () => {
const r = parseToAst(
"ALTER TABLE t FORCE DROP PARTITION LIST '2024-01-01', '2024-01-02'",
)
expect(r.errors).toHaveLength(0)
const s = r.ast[0] as AST.AlterTableStatement
expect(s.action).toEqual({
actionType: "forceDropPartition",
partitions: ["2024-01-01", "2024-01-02"],
})
})
it("rejects FORCE REINDEX without PARQUET", () => {
expect(
parseToAst("ALTER TABLE t FORCE REINDEX COLUMN b").errors.length,
).toBeGreaterThan(0)
})
it("rejects FORCE REINDEX PARQUET PARTITION without LIST", () => {
expect(
parseToAst("ALTER TABLE t FORCE REINDEX PARQUET PARTITION '2024-01-01'")
.errors.length,
).toBeGreaterThan(0)
})
})

describe("SHOW CREATE DATABASE — parse & AST", () => {
it("bare", () => {
const s = parseToAst("SHOW CREATE DATABASE").ast[0] as AST.ShowStatement
Expand Down Expand Up @@ -8302,6 +8356,25 @@ orders PIVOT (sum(amount) FOR status IN ('open'))`
}
})

describe("FORCE REINDEX PARQUET (QuestDB #8139) — round-trip", () => {
const queries = [
"alter table t force reindex parquet",
"alter table t force reindex parquet column b",
"alter table t force reindex parquet partition list '2024-01-01', '2024-01-02'",
"alter table t force reindex parquet column b partition list '2024-01-02'",
"alter table t force drop partition list '2024-01-01', '2024-01-02'",
]
for (const query of queries) {
it(`round-trips: ${query.slice(0, 64)}`, () => {
const r = parseToAst(query)
expect(r.errors, JSON.stringify(r.errors)).toHaveLength(0)
const r2 = parseToAst(toSql(r.ast[0]))
expect(r2.errors).toHaveLength(0)
expect(r2.ast[0]).toEqual(r.ast[0])
})
}
})

describe("SHOW CREATE DATABASE (QuestDB #7232) — round-trip", () => {
const queries = [
"SHOW CREATE DATABASE",
Expand Down
Loading