This commit is contained in:
2026-05-07 17:36:03 +03:00
parent 096fb1a3e2
commit 59e283945c
5 changed files with 126 additions and 49 deletions

View File

@@ -552,14 +552,31 @@ public class MainVerticle extends AbstractVerticle {
JsonArray restaurantIdsArray = body.getJsonArray("restaurantIds", new JsonArray());
List<Integer> restaurantIds = restaurantIdsArray.stream().map(id -> (Integer) id).collect(Collectors.toList());
// Получаем active: сначала из тела, иначе из конфига, иначе true
Boolean active = body.getBoolean("active");
if (active == null && config != null) active = config.getBoolean("active", true);
if (active == null) active = true;
if (name == null || dbConnectionId == null || config == null) {
rc.response().setStatusCode(400).end("Missing required fields");
return;
}
// Сначала генерируем SQL
String tableName = config.getString("tableName");
if (tableName.isEmpty()) {
rc.response().setStatusCode(400).end("Missing required fields");
return;
}
if (!olapQueryService.isValidTableName(tableName)) {
rc.response().setStatusCode(400).end("Invalid tableName: must start with a letter and contain only letters and digits");
return;
}
Boolean finalActive = active;
olapQueryService.generateSql(config, dbConnectionId)
.compose(sql -> olapQueryService.createQuery(name, dbConnectionId, config, restaurantIds, sql))
.compose(sql -> olapQueryService.createQuery(name, dbConnectionId, config, restaurantIds, sql, finalActive))
.onSuccess(id -> rc.response().setStatusCode(201).putHeader("Content-Type", "application/json").end(new JsonObject().put("id", id).encode()))
.onFailure(err -> rc.response().setStatusCode(500).end(err.getMessage()));
});
@@ -573,13 +590,30 @@ public class MainVerticle extends AbstractVerticle {
JsonArray restaurantIdsArray = body.getJsonArray("restaurantIds", new JsonArray());
List<Integer> restaurantIds = restaurantIdsArray.stream().map(v -> (Integer) v).collect(Collectors.toList());
Boolean active = body.getBoolean("active");
if (active == null && config != null) active = config.getBoolean("active", true);
if (active == null) active = true;
if (name == null || dbConnectionId == null || config == null) {
rc.response().setStatusCode(400).end("Missing required fields");
return;
}
String tableName = config.getString("tableName");
if (tableName.isEmpty()) {
rc.response().setStatusCode(400).end("Missing required fields");
return;
}
if (!olapQueryService.isValidTableName(tableName)) {
rc.response().setStatusCode(400).end("Invalid tableName: must start with a letter and contain only letters and digits");
return;
}
Boolean finalActive = active;
olapQueryService.generateSql(config, dbConnectionId)
.compose(sql -> olapQueryService.updateQuery(id, name, dbConnectionId, config, restaurantIds, sql))
.compose(sql -> olapQueryService.updateQuery(id, name, dbConnectionId, config, restaurantIds, sql, finalActive))
.onSuccess(v -> rc.response().end())
.onFailure(err -> rc.response().setStatusCode(500).end(err.getMessage()));
});

View File

@@ -5,6 +5,7 @@ import io.vertx.core.json.JsonArray;
import io.vertx.core.json.JsonObject;
import io.vertx.sqlclient.Pool;
import io.vertx.sqlclient.Row;
import io.vertx.sqlclient.Tuple;
import io.vertx.sqlclient.templates.SqlTemplate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -33,6 +34,9 @@ public class OlapQueryService {
config_json JSON NOT NULL,
full_config_json JSON NOT NULL,
sql_text TEXT,
active BOOLEAN NOT NULL DEFAULT true,
last_run TIMESTAMP NULL,
last_run_success BOOLEAN NULL,
created TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (db_connection_id) REFERENCES external_database(id) ON DELETE RESTRICT
@@ -56,16 +60,17 @@ public class OlapQueryService {
// Создание запроса
public Future<Integer> createQuery(String name, int dbConnectionId, JsonObject config,
List<Integer> restaurantIds, String generatedSql) {
List<Integer> restaurantIds, String generatedSql, boolean active) {
JsonObject fullConfig = generateFullIikoJson(config);
Map<String, Object> params = Map.of(
"name", name,
"db_connection_id", dbConnectionId,
"config_json", config.encode(),
"full_config_json", fullConfig.encode(),
"sql_text", generatedSql != null ? generatedSql : ""
"sql_text", generatedSql != null ? generatedSql : "",
"active", active
);
String sql = "INSERT INTO olap_queries (name, db_connection_id, config_json, full_config_json, sql_text) VALUES (#{name}, #{db_connection_id}, #{config_json}, #{full_config_json}, #{sql_text})";
String sql = "INSERT INTO olap_queries (name, db_connection_id, config_json, full_config_json, sql_text, active) VALUES (#{name}, #{db_connection_id}, #{config_json}, #{full_config_json}, #{sql_text}, #{active})";
return SqlTemplate.forUpdate(pool, sql)
.execute(params)
.compose(rows -> getLastInsertId())
@@ -73,7 +78,7 @@ public class OlapQueryService {
}
public Future<Void> updateQuery(int id, String name, int dbConnectionId, JsonObject config,
List<Integer> restaurantIds, String generatedSql) {
List<Integer> restaurantIds, String generatedSql, boolean active) {
JsonObject fullConfig = generateFullIikoJson(config);
Map<String, Object> params = Map.of(
"id", id,
@@ -81,15 +86,14 @@ public class OlapQueryService {
"db_connection_id", dbConnectionId,
"config_json", config.encode(),
"full_config_json", fullConfig.encode(),
"sql_text", generatedSql != null ? generatedSql : ""
"sql_text", generatedSql != null ? generatedSql : "",
"active", active
);
String sql = "UPDATE olap_queries SET name = #{name}, db_connection_id = #{db_connection_id}, config_json = #{config_json}, full_config_json = #{full_config_json}, sql_text = #{sql_text} WHERE id = #{id}";
String sql = "UPDATE olap_queries SET name = #{name}, db_connection_id = #{db_connection_id}, config_json = #{config_json}, full_config_json = #{full_config_json}, sql_text = #{sql_text}, active = #{active} WHERE id = #{id}";
return SqlTemplate.forUpdate(pool, sql)
.execute(params)
.compose(v -> {
return pool.query("DELETE FROM olap_query_restaurants WHERE query_id = " + id).execute()
.compose(del -> linkRestaurants(id, restaurantIds));
}).mapEmpty();
.compose(v -> pool.query("DELETE FROM olap_query_restaurants WHERE query_id = " + id).execute()
.compose(del -> linkRestaurants(id, restaurantIds))).mapEmpty();
}
public JsonObject generateFullIikoJson(JsonObject clientConfig) {
@@ -139,14 +143,13 @@ public class OlapQueryService {
// Добавляем системные
allFilters.mergeIn(systemFilters);
JsonObject result = new JsonObject()
return new JsonObject()
.put("reportType", reportType)
.put("buildSummary", buildSummary)
.put("groupByRowFields", rowFields)
.put("groupByColFields", columnFields)
.put("aggregateFields", valueFields)
.put("filters", allFilters);
return result;
}
private JsonObject buildDateFilter(String reportType, String dateToStr, int daysBack) {
@@ -195,36 +198,38 @@ public class OlapQueryService {
// Получить все запросы (без config_json, для списка)
public Future<JsonArray> getAllQueries() {
String sql = """
SELECT q.id, q.name, q.db_connection_id, q.created, q.updated,
GROUP_CONCAT(r.name SEPARATOR ', ') AS restaurants,
dc.name AS db_connection_name
FROM olap_queries q
LEFT JOIN olap_query_restaurants qr ON q.id = qr.query_id
LEFT JOIN restaurants r ON qr.restaurant_id = r.id
LEFT JOIN external_database dc ON q.db_connection_id = dc.id
GROUP BY q.id
ORDER BY q.id DESC
""";
SELECT q.id, q.name, q.db_connection_id, q.active, q.last_run, q.last_run_success, q.created, q.updated,
GROUP_CONCAT(r.name SEPARATOR ', ') AS restaurants,
dc.name AS db_connection_name
FROM olap_queries q
LEFT JOIN olap_query_restaurants qr ON q.id = qr.query_id
LEFT JOIN restaurants r ON qr.restaurant_id = r.id
LEFT JOIN external_database dc ON q.db_connection_id = dc.id
GROUP BY q.id
ORDER BY q.id DESC
""";
return pool.query(sql).execute()
.map(rows -> {
JsonArray arr = new JsonArray();
rows.forEach(row -> {
arr.add(new JsonObject()
.put("id", row.getInteger("id"))
.put("name", row.getString("name"))
.put("dbConnectionId", row.getInteger("db_connection_id"))
.put("dbConnectionName", row.getString("db_connection_name"))
.put("restaurants", row.getString("restaurants") != null ? row.getString("restaurants") : "")
.put("created", row.getLocalDateTime("created") != null ? row.getLocalDateTime("created").toString() : null)
.put("updated", row.getLocalDateTime("updated") != null ? row.getLocalDateTime("updated").toString() : null));
});
rows.forEach(row -> arr.add(new JsonObject()
.put("id", row.getInteger("id"))
.put("name", row.getString("name"))
.put("dbConnectionId", row.getInteger("db_connection_id"))
.put("dbConnectionName", row.getString("db_connection_name"))
.put("restaurants", row.getString("restaurants") != null ? row.getString("restaurants") : "")
.put("active", row.getBoolean("active"))
.put("lastRun", row.getLocalDateTime("last_run") != null ? row.getLocalDateTime("last_run").toString() : null)
.put("lastRunSuccess", row.getBoolean("last_run_success"))
.put("created", row.getLocalDateTime("created") != null ? row.getLocalDateTime("created").toString() : null)
.put("updated", row.getLocalDateTime("updated") != null ? row.getLocalDateTime("updated").toString() : null)));
return arr;
});
}
// Получить один запрос с полной конфигурацией
public Future<JsonObject> getQueryById(int id) {
String querySql = "SELECT id, name, db_connection_id, config_json, sql_text, created, updated FROM olap_queries WHERE id = ?";
String querySql = "SELECT id, name, db_connection_id, config_json, sql_text, active, created, updated FROM olap_queries WHERE id = ?";
String restaurantsSql = "SELECT restaurant_id FROM olap_query_restaurants WHERE query_id = ?";
return pool.preparedQuery(querySql).execute(io.vertx.sqlclient.Tuple.of(id))
@@ -237,6 +242,7 @@ public class OlapQueryService {
.put("dbConnectionId", row.getInteger("db_connection_id"))
.put("config", new JsonObject(row.getString("config_json")))
.put("sql", row.getString("sql_text"))
.put("active", row.getBoolean("active"))
.put("created", row.getLocalDateTime("created") != null ? row.getLocalDateTime("created").toString() : null)
.put("updated", row.getLocalDateTime("updated") != null ? row.getLocalDateTime("updated").toString() : null);
@@ -250,6 +256,12 @@ public class OlapQueryService {
});
}
// Метод для обновления статуса выполнения
public Future<Void> updateRunStatus(int queryId, boolean success) {
String sql = "UPDATE olap_queries SET last_run = NOW(), last_run_success = ? WHERE id = ?";
return pool.preparedQuery(sql).execute(Tuple.of(success, queryId)).mapEmpty();
}
// Генерация SQL на основе конфигурации и ID подключения
public Future<String> generateSql(JsonObject config, int dbConnectionId) {
return externalDataBaseService.findById(dbConnectionId)
@@ -264,4 +276,11 @@ public class OlapQueryService {
}
});
}
public boolean isValidTableName(String tableName) {
if (tableName == null) return false;
String trimmed = tableName.trim();
// Первый символ — английская буква, далее буквы или цифры
return trimmed.matches("^[A-Za-z][A-Za-z0-9]*$");
}
}