feat(postgres): add table vacuum action
This commit is contained in:
1 parent
0e0edcd206
commit
11407b537f
23 files changed
+554
-36
No files matched your search
@@ -91,6 +91,18 @@ describe("production SQL safety", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("detects VACUUM in a production PostgreSQL database", () => {
|
||||
const pg = connection({ db_type: "postgres", production_databases: ["prod_app"] });
|
||||
for (const sql of ['VACUUM "public"."orders"', 'VACUUM ANALYZE "public"."orders"', 'VACUUM FULL ANALYZE "public"."orders"']) {
|
||||
expect(assessProductionSql(sql, pg, "prod_app")).toMatchObject({ active: true, isMutation: true, databases: ["prod_app"] });
|
||||
}
|
||||
});
|
||||
|
||||
it("detects qualified VACUUM targets for database-qualified dialects", () => {
|
||||
const mysql = connection({ db_type: "mysql", production_databases: ["prod_app"] });
|
||||
expect(assessProductionSql("VACUUM FULL prod_app.orders", mysql, "staging")).toMatchObject({ active: true, isMutation: true, databases: ["prod_app"] });
|
||||
});
|
||||
|
||||
it("detects qualified procedure calls and privilege targets", () => {
|
||||
for (const sql of ["CALL prod_app.purge_users()", "CALL `prod_app`.`purge_users`()", "GRANT ALL ON prod_app.* TO 'u'@'%'", "GRANT EXECUTE ON PROCEDURE prod_app.purge_users TO 'u'@'%'"]) {
|
||||
expect(assessProductionSql(sql, connection(), "staging")).toMatchObject({ active: true, isMutation: true, databases: ["prod_app"] });
|
||||
|
||||
@@ -88,6 +88,8 @@ export const TREE_SCHEMA_TYPES = new Set<DatabaseType>([
|
||||
|
||||
export const DATABASE_OBJECT_TREE_TYPES = new Set<DatabaseType>(["jdbc"]);
|
||||
|
||||
export const PG_VACUUM_TYPES = new Set<DatabaseType>(["postgres", "gaussdb", "kwdb", "kingbase", "highgo", "uxdb", "vastbase", "opengauss"]);
|
||||
|
||||
export const PG_LIKE_STRUCTURE_TYPES = new Set<DatabaseType>(["postgres", "redshift", "gaussdb", "kwdb", "opengauss", "questdb"]);
|
||||
|
||||
export const DIAGRAM_SQL_TYPES = new Set<DatabaseType>(["mysql", "postgres", "sqlite", "rqlite", "turso", "cloudflare-d1", "sqlserver", "oracle", "redshift", "dameng", "gaussdb", "kwdb", "opengauss", "questdb", "oceanbase-oracle"]);
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { ConnectionConfig, DatabaseType, TreeNodeType } from "@/types/database";
|
||||
import { supportsDatabaseFeature } from "@/lib/database/databaseDriverManifest";
|
||||
import { canEditTableStructure } from "@/lib/table/tableStructureCapabilities";
|
||||
import { CLEARABLE_QUERY_SCHEMA_TYPES, DATABASE_OBJECT_TREE_TYPES, DATABASE_SCHEMA_QUALIFIED_TYPES, FETCH_FIRST_TYPES, PG_LIKE_STRUCTURE_TYPES, SCHEMA_AWARE_TYPES, SINGLE_DATABASE_TYPES, TREE_SCHEMA_TYPES } from "@/lib/database/databaseCapabilitySets";
|
||||
import { CLEARABLE_QUERY_SCHEMA_TYPES, DATABASE_OBJECT_TREE_TYPES, DATABASE_SCHEMA_QUALIFIED_TYPES, FETCH_FIRST_TYPES, PG_LIKE_STRUCTURE_TYPES, PG_VACUUM_TYPES, SCHEMA_AWARE_TYPES, SINGLE_DATABASE_TYPES, TREE_SCHEMA_TYPES } from "@/lib/database/databaseCapabilitySets";
|
||||
import { supportsRegisteredConnectionScopedQueryExecution, supportsRegisteredQueryTargetDatabaseListing, usesRegisteredConnectionOnlyQueryTarget } from "@/lib/database/sqlExecutionTargetRegistry";
|
||||
|
||||
export function isSchemaAware(dbType?: DatabaseType): boolean {
|
||||
@@ -196,6 +196,10 @@ export function supportsTableTruncate(dbType?: DatabaseType): boolean {
|
||||
return !!dbType && dbType !== "impala" && dbType !== "sqlite" && dbType !== "rqlite" && dbType !== "turso" && dbType !== "cloudflare-d1" && dbType !== "duckdb" && dbType !== "influxdb" && dbType !== "victoriametrics" && dbType !== "manticoresearch";
|
||||
}
|
||||
|
||||
export function supportsTableVacuum(dbType?: DatabaseType): boolean {
|
||||
return !!dbType && PG_VACUUM_TYPES.has(dbType);
|
||||
}
|
||||
|
||||
export function usesPostgresLikeStructureCopy(dbType?: DatabaseType): boolean {
|
||||
return !!dbType && PG_LIKE_STRUCTURE_TYPES.has(dbType);
|
||||
}
|
||||
|
||||
@@ -18,6 +18,14 @@ export interface TableAdminSqlOptions {
|
||||
cascade?: boolean;
|
||||
}
|
||||
|
||||
export interface VacuumTableSqlOptions {
|
||||
databaseType?: DatabaseType;
|
||||
schema?: string | null;
|
||||
tableName: string;
|
||||
full?: boolean;
|
||||
analyze?: boolean;
|
||||
}
|
||||
|
||||
export interface MysqlAutoIncrementSqlOptions {
|
||||
databaseType: DatabaseType;
|
||||
driverProfile?: string | null;
|
||||
@@ -286,6 +294,10 @@ export function buildTruncateTableSql(options: TableAdminSqlOptions): Promise<st
|
||||
return api.buildTruncateTableSql(options);
|
||||
}
|
||||
|
||||
export function buildVacuumTableSql(options: VacuumTableSqlOptions): Promise<string> {
|
||||
return api.buildVacuumTableSql(options);
|
||||
}
|
||||
|
||||
export function buildMysqlAutoIncrementSql(options: MysqlAutoIncrementSqlOptions): Promise<string> {
|
||||
return api.buildMysqlAutoIncrementSql(options);
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ const COPY_TARGET_RE = new RegExp(String.raw`^\s*COPY\s+(${TARGET_NAME_PATTERN})
|
||||
const TRUNCATE_TARGET_RE = new RegExp(String.raw`\bTRUNCATE\s+(?:TABLE\s+)?(${TARGET_NAME_PATTERN})`, "gi");
|
||||
const RENAME_TABLE_TARGET_RE = new RegExp(String.raw`\bRENAME\s+TABLE\s+(${TARGET_NAME_PATTERN})\s+TO\s+(${TARGET_NAME_PATTERN})`, "gi");
|
||||
const MAINTENANCE_TABLE_TARGET_RE = new RegExp(String.raw`\b(?:ANALYZE|OPTIMIZE|REPAIR|CHECK)\s+(?:NO_WRITE_TO_BINLOG\s+|LOCAL\s+)?TABLE\s+(${TARGET_NAME_PATTERN})`, "gi");
|
||||
const VACUUM_TABLE_TARGET_RE = new RegExp(String.raw`\bVACUUM(?:\s+FULL)?(?:\s+ANALYZE)?\s+(${TARGET_NAME_PATTERN})`, "gi");
|
||||
const COMMENT_TARGET_RE = new RegExp(String.raw`\bCOMMENT\s+ON\s+(?:TABLE|VIEW|COLUMN|INDEX|SEQUENCE|FUNCTION|PROCEDURE|TYPE)\s+(${TARGET_NAME_PATTERN})`, "gi");
|
||||
const ROUTINE_CALL_TARGET_RE = new RegExp(String.raw`\b(?:CALL|EXEC|EXECUTE)\s+(${QUALIFIED_NAME_PATTERN})`, "gi");
|
||||
const PRIVILEGE_TARGET_RE = new RegExp(String.raw`\b(?:GRANT|REVOKE|DENY)\b[\s\S]*?\bON\s+(?:(?:TABLE|SEQUENCE|FUNCTION|PROCEDURE|ROUTINE|OBJECT)\s+|OBJECT\s*::\s*)?(${QUALIFIED_NAME_PATTERN})`, "gi");
|
||||
@@ -159,7 +160,22 @@ function referencedDatabases(statements: string[], dbType: DatabaseType, activeD
|
||||
if (!statementIsMutation) continue;
|
||||
const currentDatabase = useDatabase || normalizedActiveDatabase;
|
||||
|
||||
collectQualifiedTargetDatabases(statement, dbType, quotedIdentifiers, currentDatabase, statementDatabases, DML_TARGET_RE, DDL_OBJECT_TARGET_RE, INDEX_ON_TARGET_RE, TRUNCATE_TARGET_RE, MAINTENANCE_TABLE_TARGET_RE, COMMENT_TARGET_RE, ROUTINE_CALL_TARGET_RE, PRIVILEGE_TARGET_RE);
|
||||
collectQualifiedTargetDatabases(
|
||||
statement,
|
||||
dbType,
|
||||
quotedIdentifiers,
|
||||
currentDatabase,
|
||||
statementDatabases,
|
||||
DML_TARGET_RE,
|
||||
DDL_OBJECT_TARGET_RE,
|
||||
INDEX_ON_TARGET_RE,
|
||||
TRUNCATE_TARGET_RE,
|
||||
MAINTENANCE_TABLE_TARGET_RE,
|
||||
VACUUM_TABLE_TARGET_RE,
|
||||
COMMENT_TARGET_RE,
|
||||
ROUTINE_CALL_TARGET_RE,
|
||||
PRIVILEGE_TARGET_RE,
|
||||
);
|
||||
collectQualifiedTargetDatabaseGroups(statement, dbType, quotedIdentifiers, currentDatabase, statementDatabases, RENAME_TABLE_TARGET_RE, [1, 2]);
|
||||
for (const match of statement.matchAll(DATABASE_TARGET_RE)) {
|
||||
const database = databaseTargetKindMeansDatabase(match[1], dbType) ? normalizeTargetDatabase(match[2], quotedIdentifiers) : "";
|
||||
|
||||
Reference in new issue
Block a user