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
114 changes: 0 additions & 114 deletions .settings/org.eclipse.jdt.core.prefs

This file was deleted.

39 changes: 34 additions & 5 deletions src/sqlancer/questdb/QuestDBErrors.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,23 @@ private QuestDBErrors() {
public static List<String> getExpressionErrors() {
ArrayList<String> errors = new ArrayList<>();

// TODO (anxing)
// QuestDB-specific expression errors
errors.add("unexpected argument for function: ");
errors.add("unexpected token:"); // SELECT FROM multiple tables without WHERE/ JOIN clause
errors.add("boolean expression expected");
errors.add("Column name expected");
errors.add("too few arguments for 'in'");
errors.add("cannot compare TIMESTAMP with type"); // WHERE column IN with nonTIMESTAMP arg
errors.add("constant expected");
errors.add("invalid column reference");
errors.add("syntax error");
errors.add("unexpected end of statement");
errors.add("invalid operator");
errors.add("type mismatch");
errors.add("division by zero");
errors.add("invalid function call");
errors.add("missing FROM clause");
errors.add("duplicate column name");

return errors;
}
Expand All @@ -30,9 +39,14 @@ public static void addExpressionErrors(ExpectedErrors errors) {
}

public static List<String> getGroupByErrors() {
// TODO (anxing)

return new ArrayList<>();
ArrayList<String> errors = new ArrayList<>();

// QuestDB-specific GROUP BY errors
errors.add("GROUP BY expression must be in SELECT list");
errors.add("aggregate function not allowed in GROUP BY");
errors.add("column must appear in GROUP BY clause");

return errors;
}

public static void addGroupByErrors(ExpectedErrors errors) {
Expand All @@ -42,15 +56,30 @@ public static void addGroupByErrors(ExpectedErrors errors) {
public static List<String> getInsertErrors() {
ArrayList<String> errors = new ArrayList<>();

// TODO (anxing)
// QuestDB-specific insert errors
errors.add("Invalid column");
errors.add("inconvertible types:");
errors.add("inconvertible value:");
errors.add("column count mismatch");
errors.add("duplicate key value");
errors.add("constraint violation");
errors.add("invalid data type");
errors.add("value too large");
errors.add("missing required column");
errors.add("table does not exist");
errors.add("permission denied");
errors.add("syntax error in INSERT statement");
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Syntax errors are always unexpected, so we should fix this in the generator.


return errors;
}

public static void addInsertErrors(ExpectedErrors errors) {
errors.addAll(getInsertErrors());
}

public static void addAllErrors(ExpectedErrors errors) {
addExpressionErrors(errors);
addGroupByErrors(errors);
addInsertErrors(errors);
}
}
57 changes: 32 additions & 25 deletions src/sqlancer/questdb/QuestDBProvider.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,20 +23,22 @@
import sqlancer.questdb.gen.QuestDBInsertGenerator;
import sqlancer.questdb.gen.QuestDBTableGenerator;
import sqlancer.questdb.gen.QuestDBTruncateGenerator;
import sqlancer.questdb.gen.QuestDBUpdateGenerator;
import sqlancer.questdb.gen.QuestDBViewGenerator;

@AutoService(DatabaseProvider.class)
public class QuestDBProvider extends SQLProviderAdapter<QuestDBGlobalState, QuestDBOptions> {
public QuestDBProvider() {
super(QuestDBGlobalState.class, QuestDBOptions.class);
}


public enum Action implements AbstractAction<QuestDBGlobalState> {
INSERT(QuestDBInsertGenerator::getQuery), //
ALTER_INDEX(QuestDBAlterIndexGenerator::getQuery), //
TRUNCATE(QuestDBTruncateGenerator::generate); //
// TODO (anxing): maybe implement these later
// UPDATE(QuestDBUpdateGenerator::getQuery), //
// CREATE_VIEW(QuestDBViewGenerator::generate), //
TRUNCATE(QuestDBTruncateGenerator::generate), //
UPDATE(QuestDBUpdateGenerator::getQuery), //
CREATE_VIEW(QuestDBViewGenerator::generate); //

private final SQLQueryProvider<QuestDBGlobalState> sqlQueryProvider;

Expand All @@ -59,6 +61,10 @@ private static int mapActions(QuestDBGlobalState globalState, Action a) {
return r.getInteger(0, 3);
case TRUNCATE:
return r.getInteger(0, 5);
case UPDATE:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should only add new features once we have a CI/CD that works for a given QuestDB version.

return r.getInteger(0, 3);
case CREATE_VIEW:
return r.getInteger(0, 2);
default:
throw new AssertionError("Unknown action: " + a);
}
Expand All @@ -72,6 +78,7 @@ protected QuestDBSchema readSchema() throws SQLException {
}

}


@Override
public void generateDatabase(QuestDBGlobalState globalState) throws Exception {
Expand Down Expand Up @@ -104,47 +111,47 @@ public SQLConnection createDatabase(QuestDBGlobalState globalState) throws Excep
if (port == sqlancer.MainOptions.NO_SET_PORT) {
port = QuestDBOptions.DEFAULT_PORT;
}
// TODO(anxing): maybe not hardcode here...

String databaseName = "qdb";
String tableName = "sqlancer_test";
String url = String.format("jdbc:postgresql://%s:%d/%s", host, port, databaseName);
// use QuestDB default username & password for Postgres JDBC

// Use QuestDB default username & password for Postgres JDBC
Properties properties = new Properties();
properties.setProperty("user", globalState.getDbmsSpecificOptions().getUserName());
properties.setProperty("password", globalState.getDbmsSpecificOptions().getPassword());
properties.setProperty("sslmode", "disable");

Connection con = DriverManager.getConnection(url, properties);
// QuestDB cannot create or drop `DATABASE`, can only create or drop `TABLE`
globalState.getState().logStatement("DROP TABLE IF EXISTS " + tableName + " CASCADE");

// QuestDB cannot create or drop DATABASE, can only create or drop TABLE
// Clean up any existing test tables
cleanupTestTables(con);

// Create a test table to verify connection
String tableName = "sqlancer_test";
SQLQueryAdapter createTableCommand = new QuestDBTableGenerator().getQuery(globalState, tableName);
globalState.getState().logStatement(createTableCommand);
globalState.getState().logStatement("DROP TABLE IF EXISTS " + tableName);

try (Statement s = con.createStatement()) {
s.execute("DROP TABLE IF EXISTS " + tableName);
}
// TODO(anxing): Drop all previous tables in db
// List<String> tableNames =
// globalState.getSchema().getDatabaseTables().stream().map(AbstractTable::getName).collect(Collectors.toList());
// for (String tName : tableNames) {
// try (Statement s = con.createStatement()) {
// String query = "DROP TABLE IF EXISTS " + tName;
// globalState.getState().logStatement(query);
// s.execute(query);
// }
// }

try (Statement s = con.createStatement()) {
s.execute(createTableCommand.getQueryString());
}
// drop test table

// Drop the test table after creation
try (Statement s = con.createStatement()) {
s.execute("DROP TABLE IF EXISTS " + tableName);
}

con.close();
con = DriverManager.getConnection(url, properties);
return new SQLConnection(con);
}

private void cleanupTestTables(Connection con) throws SQLException {
// Clean up any existing tables that might interfere with testing
try (Statement s = con.createStatement()) {
s.execute("DROP TABLE IF EXISTS sqlancer_test CASCADE");
}
}

@Override
public String getDBMSName() {
Expand Down
63 changes: 63 additions & 0 deletions src/sqlancer/questdb/QuestDBQueryGenerator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package sqlancer.questdb;

import sqlancer.common.query.ExpectedErrors;
import sqlancer.common.query.SQLQueryAdapter;
import java.util.List;

public class QuestDBQueryGenerator {

// Generate a simple SELECT query for QuestDB
public SQLQueryAdapter generateSelect(String tableName, List<String> columns) {
StringBuilder sb = new StringBuilder();
ExpectedErrors errors = new ExpectedErrors();

sb.append("SELECT ");
if (columns == null || columns.isEmpty()) {
sb.append("*");
} else {
for (int i = 0; i < columns.size(); i++) {
sb.append(columns.get(i));
if (i < columns.size() - 1) {
sb.append(", ");
}
}
}
sb.append(" FROM ").append(tableName);
sb.append(" LIMIT 10"); // simple query without semicolon for better compatibility

// Add QuestDB-specific errors
QuestDBErrors.addExpressionErrors(errors);

return new SQLQueryAdapter(sb.toString(), errors);
}

// Generate a simple SELECT query with WHERE clause
public SQLQueryAdapter generateSelectWithWhere(String tableName, List<String> columns, String whereClause) {
StringBuilder sb = new StringBuilder();
ExpectedErrors errors = new ExpectedErrors();

sb.append("SELECT ");
if (columns == null || columns.isEmpty()) {
sb.append("*");
} else {
for (int i = 0; i < columns.size(); i++) {
sb.append(columns.get(i));
if (i < columns.size() - 1) {
sb.append(", ");
}
}
}
sb.append(" FROM ").append(tableName);

if (whereClause != null && !whereClause.trim().isEmpty()) {
sb.append(" WHERE ").append(whereClause);
}

sb.append(" LIMIT 10");

// Add QuestDB-specific errors
QuestDBErrors.addExpressionErrors(errors);

return new SQLQueryAdapter(sb.toString(), errors);
}
}
Binary file added src/sqlancer/questdb/QuestDBTest.class
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like an accidental commit.

Binary file not shown.
4 changes: 2 additions & 2 deletions src/sqlancer/questdb/QuestDBToStringVisitor.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ public void visitSpecific(QuestDBExpression expr) {
visit((QuestDBConstant) expr);
} else if (expr instanceof QuestDBSelect) {
visit((QuestDBSelect) expr);
} else { // TODO: maybe implement QuestDBJoin
throw new AssertionError("Unknown class: " + expr.getClass());
} else { // QuestDB doesn't support JOINs, so no QuestDBJoin needed
throw new AssertionError("Unknown class: " + (expr != null ? expr.getClass() : "null"));
}
}

Expand Down
Loading
Loading