diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/interfaces/QueryInterface.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/interfaces/QueryInterface.java
index 674203dd..d700a2e3 100644
--- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/interfaces/QueryInterface.java
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/interfaces/QueryInterface.java
@@ -24,10 +24,10 @@ package com.kingsrook.qqq.backend.core.actions.interfaces;
import java.time.Instant;
import java.util.Set;
-import com.kingsrook.qqq.backend.core.actions.tables.helpers.querystats.QueryStat;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput;
+import com.kingsrook.qqq.backend.core.model.querystats.QueryStat;
/*******************************************************************************
@@ -67,7 +67,7 @@ public interface QueryInterface
QueryStat queryStat = getQueryStat();
if(queryStat != null)
{
- queryStat.setJoinTables(joinTableNames);
+ queryStat.setJoinTableNames(joinTableNames);
}
}
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/tables/QueryAction.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/tables/QueryAction.java
index af6575dc..01e3d4ae 100644
--- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/tables/QueryAction.java
+++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/tables/QueryAction.java
@@ -39,8 +39,7 @@ import com.kingsrook.qqq.backend.core.actions.customizers.TableCustomizers;
import com.kingsrook.qqq.backend.core.actions.interfaces.QueryInterface;
import com.kingsrook.qqq.backend.core.actions.reporting.BufferedRecordPipe;
import com.kingsrook.qqq.backend.core.actions.reporting.RecordPipeBufferedWrapper;
-import com.kingsrook.qqq.backend.core.actions.tables.helpers.querystats.QueryStat;
-import com.kingsrook.qqq.backend.core.actions.tables.helpers.querystats.QueryStatManager;
+import com.kingsrook.qqq.backend.core.actions.tables.helpers.QueryStatManager;
import com.kingsrook.qqq.backend.core.actions.values.QPossibleValueTranslator;
import com.kingsrook.qqq.backend.core.actions.values.QValueFormatter;
import com.kingsrook.qqq.backend.core.context.QContext;
@@ -52,12 +51,15 @@ import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryOutput;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
+import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.fields.AdornmentType;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.joins.JoinOn;
import com.kingsrook.qqq.backend.core.model.metadata.joins.QJoinMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.tables.Association;
+import com.kingsrook.qqq.backend.core.model.metadata.tables.Capability;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
+import com.kingsrook.qqq.backend.core.model.querystats.QueryStat;
import com.kingsrook.qqq.backend.core.modules.backend.QBackendModuleDispatcher;
import com.kingsrook.qqq.backend.core.modules.backend.QBackendModuleInterface;
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
@@ -92,12 +94,14 @@ public class QueryAction
throw (new QException("Table name was not specified in query input"));
}
- if(queryInput.getTable() == null)
+ QTableMetaData table = queryInput.getTable();
+ if(table == null)
{
throw (new QException("A table named [" + queryInput.getTableName() + "] was not found in the active QInstance"));
}
- postQueryRecordCustomizer = QCodeLoader.getTableCustomizer(AbstractPostQueryCustomizer.class, queryInput.getTable(), TableCustomizers.POST_QUERY_RECORD.getRole());
+ QBackendMetaData backend = queryInput.getBackend();
+ postQueryRecordCustomizer = QCodeLoader.getTableCustomizer(AbstractPostQueryCustomizer.class, table, TableCustomizers.POST_QUERY_RECORD.getRole());
this.queryInput = queryInput;
if(queryInput.getRecordPipe() != null)
@@ -114,13 +118,17 @@ public class QueryAction
}
}
- QueryStat queryStat = new QueryStat();
- queryStat.setTableName(queryInput.getTableName());
- queryStat.setQQueryFilter(Objects.requireNonNullElse(queryInput.getFilter(), new QQueryFilter()));
- queryStat.setStartTimestamp(Instant.now());
+ QueryStat queryStat = null;
+ if(table.isCapabilityEnabled(backend, Capability.QUERY_STATS))
+ {
+ queryStat = new QueryStat();
+ queryStat.setTableName(queryInput.getTableName());
+ queryStat.setQueryFilter(Objects.requireNonNullElse(queryInput.getFilter(), new QQueryFilter()));
+ queryStat.setStartTimestamp(Instant.now());
+ }
QBackendModuleDispatcher qBackendModuleDispatcher = new QBackendModuleDispatcher();
- QBackendModuleInterface qModule = qBackendModuleDispatcher.getQBackendModule(queryInput.getBackend());
+ QBackendModuleInterface qModule = qBackendModuleDispatcher.getQBackendModule(backend);
// todo pre-customization - just get to modify the request?
QueryInterface queryInterface = qModule.getQueryInterface();
@@ -129,7 +137,10 @@ public class QueryAction
// todo post-customization - can do whatever w/ the result if you want?
- QueryStatManager.getInstance().add(queryStat);
+ if(queryStat != null)
+ {
+ QueryStatManager.getInstance().add(queryStat);
+ }
if(queryInput.getRecordPipe() instanceof BufferedRecordPipe bufferedRecordPipe)
{
diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/tables/helpers/querystats/QueryStatManager.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/tables/helpers/querystats/QueryStatManager.java
deleted file mode 100644
index eda40a98..00000000
--- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/actions/tables/helpers/querystats/QueryStatManager.java
+++ /dev/null
@@ -1,217 +0,0 @@
-/*
- * QQQ - Low-code Application Framework for Engineers.
- * Copyright (C) 2021-2023. Kingsrook, LLC
- * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
- * contact@kingsrook.com
- * https://github.com/Kingsrook/
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 of the
- * License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see .
- */
-
-package com.kingsrook.qqq.backend.core.actions.tables.helpers.querystats;
-
-
-import java.time.Instant;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.concurrent.Executors;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.TimeUnit;
-import java.util.function.Supplier;
-import com.kingsrook.qqq.backend.core.actions.tables.InsertAction;
-import com.kingsrook.qqq.backend.core.context.QContext;
-import com.kingsrook.qqq.backend.core.logging.QLogger;
-import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
-import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
-import com.kingsrook.qqq.backend.core.model.session.QSession;
-import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
-
-
-/*******************************************************************************
- **
- *******************************************************************************/
-public class QueryStatManager
-{
- private static QueryStatManager queryStatManager = null;
-
- // todo - support multiple qInstances?
- private QInstance qInstance;
- private Supplier sessionSupplier;
-
- private boolean active = false;
- private List queryStats = new ArrayList<>();
-
- private ScheduledExecutorService executorService;
-
-
-
- /*******************************************************************************
- ** Singleton constructor
- *******************************************************************************/
- private QueryStatManager()
- {
-
- }
-
-
-
- /*******************************************************************************
- ** Singleton accessor
- *******************************************************************************/
- public static QueryStatManager getInstance()
- {
- if(queryStatManager == null)
- {
- queryStatManager = new QueryStatManager();
- }
- return (queryStatManager);
- }
-
-
-
- /*******************************************************************************
- **
- *******************************************************************************/
- public void start(Supplier sessionSupplier)
- {
- qInstance = QContext.getQInstance();
- this.sessionSupplier = sessionSupplier;
-
- active = true;
- queryStats = new ArrayList<>();
-
- executorService = Executors.newSingleThreadScheduledExecutor();
- executorService.scheduleAtFixedRate(new QueryStatManagerInsertJob(), 60, 60, TimeUnit.SECONDS);
- }
-
-
-
- /*******************************************************************************
- **
- *******************************************************************************/
- public void stop()
- {
- active = false;
- queryStats.clear();
-
- if(executorService != null)
- {
- executorService.shutdown();
- executorService = null;
- }
- }
-
-
-
- /*******************************************************************************
- **
- *******************************************************************************/
- public void add(QueryStat queryStat)
- {
- if(active)
- {
- synchronized(this)
- {
- if(queryStat.getFirstResultTimestamp() == null)
- {
- ////////////////////////////////////////////////
- // in case it didn't get set in the interface //
- ////////////////////////////////////////////////
- queryStat.setFirstResultTimestamp(Instant.now());
- }
-
- ///////////////////////////////////////////////
- // compute the millis (so you don't have to) //
- ///////////////////////////////////////////////
- if(queryStat.getStartTimestamp() != null && queryStat.getFirstResultTimestamp() != null && queryStat.getFirstResultMillis() == null)
- {
- long millis = queryStat.getFirstResultTimestamp().toEpochMilli() - queryStat.getStartTimestamp().toEpochMilli();
- queryStat.setFirstResultMillis((int) millis);
- }
-
- queryStats.add(queryStat);
- }
- }
- }
-
-
-
- /*******************************************************************************
- **
- *******************************************************************************/
- private List getListAndReset()
- {
- if(queryStats.isEmpty())
- {
- return Collections.emptyList();
- }
-
- synchronized(this)
- {
- List returnList = queryStats;
- queryStats = new ArrayList<>();
- return (returnList);
- }
- }
-
-
-
- /*******************************************************************************
- **
- *******************************************************************************/
- private static class QueryStatManagerInsertJob implements Runnable
- {
- private static final QLogger LOG = QLogger.getLogger(QueryStatManagerInsertJob.class);
-
-
-
- /*******************************************************************************
- **
- *******************************************************************************/
- @Override
- public void run()
- {
- try
- {
- QContext.init(getInstance().qInstance, getInstance().sessionSupplier.get());
-
- List list = getInstance().getListAndReset();
- LOG.info(logPair("queryStatListSize", list.size()));
-
- if(list.isEmpty())
- {
- return;
- }
-
- try
- {
- InsertInput insertInput = new InsertInput();
- insertInput.setTableName(QueryStat.TABLE_NAME);
- insertInput.setRecords(list.stream().map(qs -> qs.toQRecord()).toList());
- new InsertAction().execute(insertInput);
- }
- catch(Exception e)
- {
- LOG.error("Error inserting query stats", e);
- }
- }
- finally
- {
- QContext.clear();
- }
- }
- }
-
-}
diff --git a/qqq-backend-module-rdbms/src/main/java/com/kingsrook/qqq/backend/module/rdbms/actions/RDBMSQueryAction.java b/qqq-backend-module-rdbms/src/main/java/com/kingsrook/qqq/backend/module/rdbms/actions/RDBMSQueryAction.java
index 1e778d80..21c09d8e 100644
--- a/qqq-backend-module-rdbms/src/main/java/com/kingsrook/qqq/backend/module/rdbms/actions/RDBMSQueryAction.java
+++ b/qqq-backend-module-rdbms/src/main/java/com/kingsrook/qqq/backend/module/rdbms/actions/RDBMSQueryAction.java
@@ -30,12 +30,13 @@ import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import java.util.stream.Collectors;
import com.kingsrook.qqq.backend.core.actions.interfaces.QueryInterface;
-import com.kingsrook.qqq.backend.core.actions.tables.helpers.querystats.QueryStat;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.logging.QLogger;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.JoinsContext;
@@ -48,6 +49,7 @@ import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
+import com.kingsrook.qqq.backend.core.model.querystats.QueryStat;
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
import com.kingsrook.qqq.backend.core.utils.Pair;
import com.kingsrook.qqq.backend.module.rdbms.jdbc.QueryManager;
@@ -142,12 +144,29 @@ public class RDBMSQueryAction extends AbstractRDBMSAction implements QueryInterf
//////////////////////////////////////////////
QueryOutput queryOutput = new QueryOutput(queryInput);
+ if(queryStat != null)
+ {
+ queryStat.setQueryText(sql.toString());
+
+ if(CollectionUtils.nullSafeHasContents(joinsContext.getQueryJoins()))
+ {
+ Set joinTableNames = new HashSet<>();
+ for(QueryJoin queryJoin : joinsContext.getQueryJoins())
+ {
+ joinTableNames.add(queryJoin.getJoinTable());
+ }
+ setQueryStatJoinTables(joinTableNames);
+ }
+ }
+
PreparedStatement statement = createStatement(connection, sql.toString(), queryInput);
QueryManager.executeStatement(statement, ((ResultSet resultSet) ->
{
ResultSetMetaData metaData = resultSet.getMetaData();
while(resultSet.next())
{
+ setQueryStatFirstResultTime();
+
QRecord record = new QRecord();
record.setTableName(table.getName());
LinkedHashMap values = new LinkedHashMap<>();