Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

QuestDB: add types, seek to extend code to find issues in QuestDB #798

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/sqlancer/Main.java
Original file line number Diff line number Diff line change
Expand Up @@ -352,8 +352,8 @@ public O getCommand() {

public void testConnection() throws Exception {
G state = getInitializedGlobalState(options.getRandomSeed());
try (SQLancerDBConnection con = provider.createDatabase(state)) {
return;
try (SQLancerDBConnection ignore = provider.createDatabase(state)) {
// no op
}
}

Expand Down
1 change: 0 additions & 1 deletion src/sqlancer/Randomly.java
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,6 @@ public static <T> T fromList(List<T> list) {
return list.get((int) getNextLong(0, list.size()));
}

@SafeVarargs
public static <T> T fromOptions(T... options) {
return options[getNextInt(0, options.length)];
}
Expand Down
4 changes: 2 additions & 2 deletions src/sqlancer/mariadb/gen/MariaDBSetGenerator.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,11 @@ private enum Action {

AUTOCOMMIT("autocommit", (r) -> 1, Scope.GLOBAL, Scope.SESSION), //
BIG_TABLES("big_tables", (r) -> Randomly.fromOptions("OFF", "ON"), Scope.GLOBAL, Scope.SESSION), //
COMPLETION_TYPE("completion_type", (r) -> Randomly.fromOptions("'NO_CHAIN'", "'CHAIN'", "'RELEASE'", 0, 1, 2),
COMPLETION_TYPE("completion_type", (r) -> Randomly.fromOptions("'NO_CHAIN'", "'CHAIN'", "'RELEASE'", "0", "1", "2"),
Copy link
Contributor

Choose a reason for hiding this comment

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

What's the motivation for these changes? Any IDE warning or so?

Copy link
Author

Choose a reason for hiding this comment

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

hi! yes, warnings in intelliJ. I will resume with the PR soon, another priority kicked in :), you know how these things are.

Scope.GLOBAL), //
// BULK_INSERT_CACHE_SIZE("bulk_insert_buffer_size", (r) -> r.getLong(0, Long.MAX_VALUE), Scope.GLOBAL,
// Scope.SESSION),
CONCURRENT_INSERT("concurrent_insert", (r) -> Randomly.fromOptions("NEVER", "AUTO", "ALWAYS", 0, 1, 2),
CONCURRENT_INSERT("concurrent_insert", (r) -> Randomly.fromOptions("NEVER", "AUTO", "ALWAYS", "0", "1", "2"),
Scope.GLOBAL),
CTE_MAX_RECURSION_DEPTH("cte_max_recursion_depth", (r) -> r.getLong(0, 4294967295L), Scope.GLOBAL),
DELAY_KEY_WRITE("delay_key_write", (r) -> Randomly.fromOptions("ON", "OFF", "ALL"), Scope.GLOBAL),
Expand Down
6 changes: 3 additions & 3 deletions src/sqlancer/mysql/gen/MySQLAlterTable.java
Original file line number Diff line number Diff line change
Expand Up @@ -130,15 +130,15 @@ private SQLQueryAdapter create() {
break;
case STATS_AUTO_RECALC:
sb.append("STATS_AUTO_RECALC ");
sb.append(Randomly.fromOptions(0, 1, "DEFAULT"));
sb.append(Randomly.fromOptions("0", "1", "DEFAULT"));
break;
case STATS_PERSISTENT:
sb.append("STATS_PERSISTENT ");
sb.append(Randomly.fromOptions(0, 1, "DEFAULT"));
sb.append(Randomly.fromOptions("0", "1", "DEFAULT"));
break;
case PACK_KEYS:
sb.append("PACK_KEYS ");
sb.append(Randomly.fromOptions(0, 1, "DEFAULT"));
sb.append(Randomly.fromOptions("0", "1", "DEFAULT"));
break;
// not relevant:
// case WITH_WITHOUT_VALIDATION:
Expand Down
4 changes: 2 additions & 2 deletions src/sqlancer/mysql/gen/MySQLSetGenerator.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,11 @@ private enum Action {

AUTOCOMMIT("autocommit", (r) -> 1, Scope.GLOBAL, Scope.SESSION), //
BIG_TABLES("big_tables", (r) -> Randomly.fromOptions("OFF", "ON"), Scope.GLOBAL, Scope.SESSION), //
COMPLETION_TYPE("completion_type", (r) -> Randomly.fromOptions("'NO_CHAIN'", "'CHAIN'", "'RELEASE'", 0, 1, 2),
COMPLETION_TYPE("completion_type", (r) -> Randomly.fromOptions("'NO_CHAIN'", "'CHAIN'", "'RELEASE'", "0", "1", "2"),
Scope.GLOBAL), //
BULK_INSERT_CACHE_SIZE("bulk_insert_buffer_size", (r) -> r.getLong(0, Long.MAX_VALUE), Scope.GLOBAL, //
Scope.SESSION), //
CONCURRENT_INSERT("concurrent_insert", (r) -> Randomly.fromOptions("NEVER", "AUTO", "ALWAYS", 0, 1, 2), //
CONCURRENT_INSERT("concurrent_insert", (r) -> Randomly.fromOptions("NEVER", "AUTO", "ALWAYS", "0", "1", "2"), //
Scope.GLOBAL), //
CTE_MAX_RECURSION_DEPTH("cte_max_recursion_depth", //
(r) -> r.getLong(0, 4294967295L), Scope.GLOBAL), //
Expand Down
48 changes: 48 additions & 0 deletions src/sqlancer/questdb/QuestDBDataType.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package sqlancer.questdb;

import sqlancer.Randomly;

/**
* Refer to <a href="https://questdb.io/docs/reference/sql/datatypes/">QuestDB data types</a>.
* <p></p>
* Type attributes: bitSize, isNullable.
*/
public enum QuestDBDataType {
BOOLEAN(1, false), // null becomes false
BYTE(8, false), // null becomes 0
SHORT(16, false), // null becomes 0
CHAR(16),
INT(32),
LONG(64),
DATE(64),
TIMESTAMP(64),
FLOAT(32),
DOUBLE(64),
STRING(-1), // variable size: 32 + 16 * n
SYMBOL(32),

// not strictly a type. QuestDB stores a type dependent value in case of NULL.
NULL(-1); // variable size

public final int bitSize; // -1 means size is variable
public final boolean isNullable;

QuestDBDataType(int bitSize) {
this(bitSize, true);
}

QuestDBDataType(int bitSize, boolean isNullable) {
this.bitSize = bitSize;
this.isNullable = isNullable;
}

public static QuestDBDataType getNonNullRandom() {
// NOTE: NULL is not a type, it is used as a marker only.
QuestDBDataType[] types = QuestDBDataType.values();
QuestDBDataType type;
do {
type = Randomly.fromOptions(types);
} while (type == QuestDBDataType.NULL);
return type;
}
}
138 changes: 64 additions & 74 deletions src/sqlancer/questdb/QuestDBProvider.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
import java.util.concurrent.atomic.AtomicBoolean;

import com.google.auto.service.AutoService;

Expand All @@ -26,71 +27,49 @@

@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), //
private static final String JDBC_DRIVER_URL_FORMAT = "jdbc:postgresql://%s:%s/qdb";
private static final String TEST_TABLE_NAME = "sqlancer_test";
private static final String DROP_DATABASE_STMT = "DROP DATABASE";

private final SQLQueryProvider<QuestDBGlobalState> sqlQueryProvider;
private static final AtomicBoolean HAS_CLEARED_DATABASE = new AtomicBoolean();

Action(SQLQueryProvider<QuestDBGlobalState> sqlQueryProvider) {
this.sqlQueryProvider = sqlQueryProvider;
}
private final QuestDBTableGenerator tableGenerator = new QuestDBTableGenerator();

@Override
public SQLQueryAdapter getQuery(QuestDBGlobalState state) throws Exception {
return sqlQueryProvider.getQuery(state);
}
public QuestDBProvider() {
super(QuestDBGlobalState.class, QuestDBOptions.class);
}

private static int mapActions(QuestDBGlobalState globalState, Action a) {
Randomly r = globalState.getRandomly();
switch (a) {
case INSERT:
return r.getInteger(0, globalState.getOptions().getMaxNumberInserts());
case ALTER_INDEX:
return r.getInteger(0, 3);
case TRUNCATE:
return r.getInteger(0, 5);
default:
throw new AssertionError("Unknown action: " + a);
}
}

public static class QuestDBGlobalState extends SQLGlobalState<QuestDBOptions, QuestDBSchema> {

@Override
protected QuestDBSchema readSchema() throws SQLException {
return QuestDBSchema.fromConnection(getConnection(), getDatabaseName());
case INSERT:
return r.getInteger(0, globalState.getOptions().getMaxNumberInserts());
case ALTER_INDEX:
return r.getInteger(0, 3);
case TRUNCATE:
return r.getInteger(0, 5);
default:
throw new AssertionError("Unknown action: " + a);
}

}

@Override
public void generateDatabase(QuestDBGlobalState globalState) throws Exception {
for (int i = 0; i < Randomly.fromOptions(1, 2); i++) {
boolean success;
do {
SQLQueryAdapter qt = new QuestDBTableGenerator().getQuery(globalState, null);
success = globalState.executeStatement(qt);
success = globalState.executeStatement(tableGenerator.getQuery(globalState, null));
} while (!success);
}
if (globalState.getSchema().getDatabaseTables().isEmpty()) {
throw new IgnoreMeException();
}
StatementExecutor<QuestDBGlobalState, Action> se = new StatementExecutor<>(globalState, Action.values(),
QuestDBProvider::mapActions, (q) -> {
if (globalState.getSchema().getDatabaseTables().isEmpty()) {
throw new IgnoreMeException();
}
});
if (globalState.getSchema().getDatabaseTables().isEmpty()) {
throw new IgnoreMeException();
}
});
se.executeStatements();
}

Expand All @@ -104,45 +83,30 @@ 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
String url = String.format(JDBC_DRIVER_URL_FORMAT, host, port);
Properties properties = new Properties();
properties.setProperty("user", globalState.getDbmsSpecificOptions().getUserName());
properties.setProperty("password", globalState.getDbmsSpecificOptions().getPassword());
properties.setProperty("sslmode", "disable");
properties.setProperty("sslmode", "prefer");

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");
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
try (Statement s = con.createStatement()) {
s.execute("DROP TABLE IF EXISTS " + tableName);

if (HAS_CLEARED_DATABASE.compareAndSet(false, true)) {
// drop database
globalState.getState().logStatement(DROP_DATABASE_STMT);
try (Statement s = con.createStatement()) {
s.execute(DROP_DATABASE_STMT);
}

// create test table
SQLQueryAdapter createTableCommand = tableGenerator.getQuery(globalState, TEST_TABLE_NAME);
globalState.getState().logStatement(createTableCommand);
try (Statement s = con.createStatement()) {
s.execute(createTableCommand.getQueryString());
}
}
con.close();
con = DriverManager.getConnection(url, properties);
return new SQLConnection(con);
}

Expand All @@ -151,4 +115,30 @@ public String getDBMSName() {
return "questdb";
}

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

private final SQLQueryProvider<QuestDBGlobalState> sqlQueryProvider;

Action(SQLQueryProvider<QuestDBGlobalState> sqlQueryProvider) {
this.sqlQueryProvider = sqlQueryProvider;
}

@Override
public SQLQueryAdapter getQuery(QuestDBGlobalState state) throws Exception {
return sqlQueryProvider.getQuery(state);
}
}

public static class QuestDBGlobalState extends SQLGlobalState<QuestDBOptions, QuestDBSchema> {
@Override
protected QuestDBSchema readSchema() throws SQLException {
return QuestDBSchema.fromConnection(getConnection());
}
}
}