Skip to content

Commit

Permalink
QuestDB: add types, seek to extend code to find issues in QuestDB
Browse files Browse the repository at this point in the history
  • Loading branch information
marregui committed Jun 11, 2023
1 parent d013225 commit 1c61cd3
Show file tree
Hide file tree
Showing 20 changed files with 1,056 additions and 594 deletions.
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"),
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());
}
}
}

0 comments on commit 1c61cd3

Please sign in to comment.