diff --git a/src/sqlancer/postgres/PostgresGlobalState.java b/src/sqlancer/postgres/PostgresGlobalState.java index 0f2c7ebdb..a5cdceec6 100644 --- a/src/sqlancer/postgres/PostgresGlobalState.java +++ b/src/sqlancer/postgres/PostgresGlobalState.java @@ -27,6 +27,7 @@ public class PostgresGlobalState extends SQLGlobalState functionsAndTypes = new HashMap<>(); private List allowedFunctionTypes = Arrays.asList(IMMUTABLE, STABLE, VOLATILE); + private int majorVersion; @Override public void setConnection(SQLConnection con) { @@ -36,6 +37,7 @@ public void setConnection(SQLConnection con) { this.operators = getOperators(getConnection()); this.collates = getCollnames(getConnection()); this.tableAccessMethods = getTableAccessMethods(getConnection()); + this.majorVersion = getMajorVersion(getConnection()); } catch (SQLException e) { throw new AssertionError(e); } @@ -93,6 +95,19 @@ private List getTableAccessMethods(SQLConnection con) throws SQLExceptio return tableAccessMethods; } + private int getMajorVersion(SQLConnection con) throws SQLException { + try (Statement s = con.createStatement()) { + try (ResultSet rs = s.executeQuery("SHOW server_version_num;")) { + if (rs.next()) { + // Server version returns a number like 130018 for 13.18 + // Divide by 10000 to get major version (13) + return rs.getInt(1) / 10000; + } + } + } + return 0; // Default to 0 if version cannot be determined + } + public List getOperators() { return operators; } @@ -150,4 +165,8 @@ public List getAllowedFunctionTypes() { return this.allowedFunctionTypes; } + public int getMajorVersion() { + return majorVersion; + } + } diff --git a/src/sqlancer/postgres/gen/PostgresVacuumGenerator.java b/src/sqlancer/postgres/gen/PostgresVacuumGenerator.java index ef32db45a..67b02f743 100644 --- a/src/sqlancer/postgres/gen/PostgresVacuumGenerator.java +++ b/src/sqlancer/postgres/gen/PostgresVacuumGenerator.java @@ -10,6 +10,12 @@ import sqlancer.postgres.PostgresGlobalState; import sqlancer.postgres.PostgresSchema.PostgresTable; +/** + * Generates VACUUM commands for PostgreSQL. + * This implementation supports PostgreSQL 13 features including the PARALLEL option. + * The PARALLEL option specifies the number of parallel worker processes to use when running + * a parallel vacuum (only applies to the collection of table data, not indexes). + */ public final class PostgresVacuumGenerator { private PostgresVacuumGenerator() { @@ -25,12 +31,27 @@ public static SQLQueryAdapter create(PostgresGlobalState globalState) { for (int i = 0; i < Randomly.smallNumber() + 1; i++) { ArrayList opts = new ArrayList<>(Arrays.asList("FULL", "FREEZE", "ANALYZE", "VERBOSE", "DISABLE_PAGE_SKIPPING", "SKIP_LOCKED", "INDEX_CLEANUP", "TRUNCATE")); + + // Add PARALLEL option if PostgreSQL version is 13 or higher + int majorVersion = globalState.getMajorVersion(); + if (majorVersion >= 13) { + opts.add("PARALLEL"); + } + String option = Randomly.fromList(opts); if (i != 0) { sb.append(", "); } sb.append(option); - if (Randomly.getBoolean()) { + + // For PostgreSQL v13+, PARALLEL always needs a value + if (option.equals("PARALLEL") && majorVersion >= 13) { + // PARALLEL takes integer value between 0 and 1024 + sb.append(" "); + sb.append(Randomly.getNotCachedInteger(0, 4)); // Use reasonable value for testing + } + // For other options, randomly decide whether to append a value + else if (Randomly.getBoolean()) { sb.append(" "); sb.append(Randomly.fromOptions(1, 0)); } @@ -60,6 +81,12 @@ public static SQLQueryAdapter create(PostgresGlobalState globalState) { */ errors.add("ERROR: ANALYZE option must be specified when a column list is provided"); errors.add("VACUUM option DISABLE_PAGE_SKIPPING cannot be used with FULL"); + + // Add error specific to PostgreSQL v13's PARALLEL option + if (globalState.getMajorVersion() >= 13) { + errors.add("VACUUM option PARALLEL cannot be used with FULL"); + } + return new SQLQueryAdapter(sb.toString(), errors); }