diff --git a/server/bundles/io.cloudbeaver.model/OSGI-INF/l10n/bundle.properties b/server/bundles/io.cloudbeaver.model/OSGI-INF/l10n/bundle.properties index bcfc808dd67..206d09efb29 100644 --- a/server/bundles/io.cloudbeaver.model/OSGI-INF/l10n/bundle.properties +++ b/server/bundles/io.cloudbeaver.model/OSGI-INF/l10n/bundle.properties @@ -16,4 +16,5 @@ meta.io.cloudbeaver.model.WebExpertSettingsProperties.readOnly.name = Read-only meta.io.cloudbeaver.model.WebExpertSettingsProperties.autoCommit.name = Auto-commit meta.io.cloudbeaver.model.WebExpertSettingsProperties.keepAliveInterval.name = Keep-alive interval (seconds) meta.io.cloudbeaver.model.WebExpertSettingsProperties.defaultSchema.name = Default schema -meta.io.cloudbeaver.model.WebExpertSettingsProperties.defaultCatalog.name = Default catalog \ No newline at end of file +meta.io.cloudbeaver.model.WebExpertSettingsProperties.defaultCatalog.name = Default catalog +meta.io.cloudbeaver.model.WebExpertSettingsProperties.defaultOrdering.name = Default ordering \ No newline at end of file diff --git a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/WebConnectionConfigInputHandler.java b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/WebConnectionConfigInputHandler.java index 31c25973a9b..df10f4645e3 100644 --- a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/WebConnectionConfigInputHandler.java +++ b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/WebConnectionConfigInputHandler.java @@ -22,6 +22,7 @@ import io.cloudbeaver.utils.WebDataSourceUtils; import org.jkiss.code.NotNull; import org.jkiss.dbeaver.Log; +import org.jkiss.dbeaver.ModelPreferences; import org.jkiss.dbeaver.model.app.DBPDataSourceRegistry; import org.jkiss.dbeaver.model.connection.DBPConnectionConfiguration; import org.jkiss.dbeaver.model.connection.DBPDriver; @@ -80,6 +81,10 @@ public void updateDataSource(@NotNull C dataSource) throws DBWebException { dataSource.getConnectionConfiguration(), input ); + WebDataSourceUtils.setConnectionPreferences( + dataSource.getPreferenceStore(), + input + ); WebDataSourceUtils.saveAuthProperties( dataSource, dataSource.getConnectionConfiguration(), diff --git a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/WebConnectionConfig.java b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/WebConnectionConfig.java index 78ba2866060..57b472092a1 100644 --- a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/WebConnectionConfig.java +++ b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/WebConnectionConfig.java @@ -18,9 +18,11 @@ import org.jkiss.code.NotNull; import org.jkiss.code.Nullable; +import org.jkiss.dbeaver.ModelPreferences.OrderingPolicy; import org.jkiss.dbeaver.model.connection.DBPDriverConfigurationType; import org.jkiss.dbeaver.model.data.json.JSONUtils; import org.jkiss.dbeaver.model.meta.Property; +import org.jkiss.utils.CommonUtils; import java.util.ArrayList; import java.util.List; @@ -64,6 +66,7 @@ public class WebConnectionConfig { private boolean defaultAutoCommit; private String defaultCatalogName; private String defaultSchemaName; + private OrderingPolicy defaultOrdering; public WebConnectionConfig() { } @@ -107,6 +110,11 @@ public WebConnectionConfig(@NotNull Map params) { expertSettingsValues != null ? expertSettingsValues : params, WebExpertSettingsProperties.PROP_DEFAULT_SCHEMA ); + String defaultOrderingString = JSONUtils.getString( + expertSettingsValues != null ? expertSettingsValues : params, + WebExpertSettingsProperties.PROP_DEFAULT_ORDERING + ); + defaultOrdering = OrderingPolicy.fromString(defaultOrderingString, OrderingPolicy.DEFAULT); String configType = JSONUtils.getString(params, "configurationType"); configurationType = configType == null ? null : DBPDriverConfigurationType.valueOf(configType); @@ -259,4 +267,9 @@ public String getDefaultCatalogName() { public String getDefaultSchemaName() { return defaultSchemaName; } + + @Property + public OrderingPolicy getDefaultOrdering() { + return defaultOrdering; + } } diff --git a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/WebConnectionInfo.java b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/WebConnectionInfo.java index 3e3b386173b..be05661235f 100644 --- a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/WebConnectionInfo.java +++ b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/WebConnectionInfo.java @@ -29,6 +29,8 @@ import org.jkiss.code.Nullable; import org.jkiss.dbeaver.DBException; import org.jkiss.dbeaver.Log; +import org.jkiss.dbeaver.ModelPreferences; +import org.jkiss.dbeaver.ModelPreferences.OrderingPolicy; import org.jkiss.dbeaver.model.*; import org.jkiss.dbeaver.model.admin.sessions.DBAServerSessionManager; import org.jkiss.dbeaver.model.app.DBPProject; @@ -39,6 +41,7 @@ import org.jkiss.dbeaver.model.meta.Property; import org.jkiss.dbeaver.model.navigator.DBNBrowseSettings; import org.jkiss.dbeaver.model.navigator.DBNDataSource; +import org.jkiss.dbeaver.model.preferences.DBPPreferenceStore; import org.jkiss.dbeaver.model.preferences.DBPPropertyDescriptor; import org.jkiss.dbeaver.model.preferences.DBPPropertySource; import org.jkiss.dbeaver.model.rm.RMConstants; @@ -442,6 +445,7 @@ public Map getExpertSettingsValues() { expertSettings.put(WebExpertSettingsProperties.PROP_READ_ONLY, isReadOnly()); expertSettings.put(WebExpertSettingsProperties.PROP_DEFAULT_CATALOG, getDefaultCatalogName()); expertSettings.put(WebExpertSettingsProperties.PROP_DEFAULT_SCHEMA, getDefaultSchemaName()); + expertSettings.put(WebExpertSettingsProperties.PROP_DEFAULT_ORDERING, getDefaultOrdering()); return expertSettings; } @@ -533,6 +537,17 @@ public String getDefaultSchemaName() { return connectionConfiguration.getBootstrap().getDefaultSchemaName(); } + @Property + @NotNull + public OrderingPolicy getDefaultOrdering() { + DBPPreferenceStore store = dataSourceContainer.getPreferenceStore(); + return CommonUtils.valueOf( + OrderingPolicy.class, + store.getString(ModelPreferences.RESULT_SET_ORDERING_POLICY), + OrderingPolicy.DEFAULT + ); + } + @Property @NotNull public List getSharedSecrets() throws DBException { diff --git a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/WebExpertSettingsProperties.java b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/WebExpertSettingsProperties.java index 86265613d30..e6767d35354 100644 --- a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/WebExpertSettingsProperties.java +++ b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/model/WebExpertSettingsProperties.java @@ -2,11 +2,14 @@ import org.jkiss.code.NotNull; import org.jkiss.code.Nullable; +import org.jkiss.dbeaver.ModelPreferences; +import org.jkiss.dbeaver.ModelPreferences.OrderingPolicy; import org.jkiss.dbeaver.model.DBPObject; import org.jkiss.dbeaver.model.connection.DBPDriver; import org.jkiss.dbeaver.model.connection.DBPDriverConstants; import org.jkiss.dbeaver.model.meta.IPropertyValueValidator; import org.jkiss.dbeaver.model.meta.Property; +import org.jkiss.dbeaver.model.preferences.DBPPreferenceStore; import org.jkiss.utils.CommonUtils; /** @@ -18,6 +21,7 @@ public class WebExpertSettingsProperties implements DBPObject { public static final String PROP_KEEP_ALIVE_INTERVAL = "keepAliveInterval"; public static final String PROP_DEFAULT_CATALOG = "defaultCatalogName"; public static final String PROP_DEFAULT_SCHEMA = "defaultSchemaName"; + public static final String PROP_DEFAULT_ORDERING = "defaultOrdering"; private final DBPDriver driver; @@ -50,6 +54,10 @@ public String getDefaultSchema() { return null; } + @Property(order = 6, id = PROP_DEFAULT_ORDERING) + public OrderingPolicy getDefaultOrdering() { + return null; + } public static class KeepAliveIntervalFieldValidator implements IPropertyValueValidator { @Override diff --git a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/utils/WebDataSourceUtils.java b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/utils/WebDataSourceUtils.java index a796ba85422..09ba2e46544 100644 --- a/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/utils/WebDataSourceUtils.java +++ b/server/bundles/io.cloudbeaver.model/src/io/cloudbeaver/utils/WebDataSourceUtils.java @@ -31,6 +31,7 @@ import org.jkiss.code.Nullable; import org.jkiss.dbeaver.DBException; import org.jkiss.dbeaver.Log; +import org.jkiss.dbeaver.ModelPreferences; import org.jkiss.dbeaver.model.DBConstants; import org.jkiss.dbeaver.model.DBPDataSourceContainer; import org.jkiss.dbeaver.model.access.DBAAuthCredentials; @@ -43,6 +44,7 @@ import org.jkiss.dbeaver.model.impl.auth.AuthModelDatabaseNativeCredentials; import org.jkiss.dbeaver.model.net.DBWHandlerConfiguration; import org.jkiss.dbeaver.model.net.ssh.SSHConstants; +import org.jkiss.dbeaver.model.preferences.DBPPreferenceStore; import org.jkiss.dbeaver.model.websocket.event.datasource.WSDataSourceDisconnectEvent; import org.jkiss.dbeaver.registry.network.NetworkHandlerDescriptor; import org.jkiss.dbeaver.registry.network.NetworkHandlerRegistry; @@ -342,6 +344,16 @@ public static void setConnectionConfiguration( } } + public static void setConnectionPreferences( + @NotNull DBPPreferenceStore store, + @NotNull WebConnectionConfig config + ) { + store.setValue( + ModelPreferences.RESULT_SET_ORDERING_POLICY, + config.getDefaultOrdering().name() + ); + } + public static void setMainProperties(@NotNull DBPConnectionConfiguration dsConfig, @NotNull WebConnectionConfig config) { if (CommonUtils.isNotEmpty(config.getUrl())) { dsConfig.setUrl(config.getUrl()); diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/sql/WebSQLDataFilter.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/sql/WebSQLDataFilter.java index 30d9b5d424a..9289f719eb8 100644 --- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/sql/WebSQLDataFilter.java +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/sql/WebSQLDataFilter.java @@ -101,6 +101,7 @@ public static WebSQLDataFilter from(DBDDataFilter filter) { return webFilter; } + @NotNull public DBDDataFilter makeDataFilter(@Nullable WebSQLResultsInfo resultInfo) throws DBException { DBDDataFilter dataFilter = new DBDDataFilter(); dataFilter.setWhere(where); diff --git a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/sql/WebSQLProcessor.java b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/sql/WebSQLProcessor.java index 5897a27387b..41b0516a098 100644 --- a/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/sql/WebSQLProcessor.java +++ b/server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/sql/WebSQLProcessor.java @@ -29,6 +29,8 @@ import org.jkiss.code.Nullable; import org.jkiss.dbeaver.DBException; import org.jkiss.dbeaver.Log; +import org.jkiss.dbeaver.ModelPreferences; +import org.jkiss.dbeaver.ModelPreferences.OrderingPolicy; import org.jkiss.dbeaver.model.DBPDataKind; import org.jkiss.dbeaver.model.DBPDataSource; import org.jkiss.dbeaver.model.DBUtils; @@ -43,6 +45,7 @@ import org.jkiss.dbeaver.model.impl.DefaultServerOutputReader; import org.jkiss.dbeaver.model.navigator.DBNDatabaseItem; import org.jkiss.dbeaver.model.navigator.DBNNode; +import org.jkiss.dbeaver.model.preferences.DBPPreferenceStore; import org.jkiss.dbeaver.model.qm.QMUtils; import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor; import org.jkiss.dbeaver.model.sql.*; @@ -72,6 +75,7 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; import java.util.stream.Collectors; /** @@ -194,13 +198,17 @@ public WebSQLExecuteInfo processQuery( DBCExecutionContext context = getExecutionContext(dataContainer); try { - final DBDDataFilter dataFilter = filter.makeDataFilter((resultId == null ? null : contextInfo.getResults(resultId))); + WebSQLResultsInfo resultsInfo = resultId == null ? null : contextInfo.getResults(resultId); + final DBDDataFilter dataFilter = filter.makeDataFilter(resultsInfo); + DBPDataSource dataSource = context.getDataSource(); + + DBPPreferenceStore store = dataSource.getContainer().getPreferenceStore(); + boolean defaultOrderingConfigured = isDefaultOrderingConfigured(store); + if (defaultOrderingConfigured && resultsInfo != null && !dataFilter.hasFilters()) { + applyDefaultOrdering(dataFilter, resultsInfo, store); + } if (dataFilter.hasFilters()) { - sql = context.getDataSource().getSQLDialect().addFiltersToQuery( - monitor, - context.getDataSource(), - sql, - dataFilter); + sql = dataSource.getSQLDialect().addFiltersToQuery(monitor, dataSource, sql, dataFilter); } final WebSQLDataFilter webDataFilter = filter; @@ -260,7 +268,6 @@ public WebSQLExecuteInfo processQuery( { SqlOutputLogReaderJob sqlOutputLogReaderJob = null; if (readLogs) { - DBPDataSource dataSource = context.getDataSource(); DBCServerOutputReader dbcServerOutputReader = DBUtils.getAdapter(DBCServerOutputReader.class, dataSource); if (dbcServerOutputReader == null) { dbcServerOutputReader = new DefaultServerOutputReader(); @@ -301,6 +308,14 @@ public WebSQLExecuteInfo processQuery( } else { executeInfo.setResults(new WebSQLQueryResults[0]); } + + if (defaultOrderingConfigured && resultsInfo == null && + !dataFilter.hasFilters() && executeInfo.getResults().length == 1 + ) { + String resultSetId = executeInfo.getResults()[0].getResultSet().getId(); + // Make the second request with initialized resultSet to apply default ordering + return processQuery(monitor, contextInfo, sql, resultSetId, filter, dataFormat, webSession, asyncTask, readLogs, useEvents); + } } catch (DBException e) { throw new DBWebException("Error executing query", e); } @@ -332,7 +347,16 @@ public WebSQLExecuteInfo readDataFromContainer( WebSQLExecuteInfo executeInfo = new WebSQLExecuteInfo(); DBCExecutionContext executionContext = DBUtils.getOrOpenDefaultContext(dataContainer, false); - DBDDataFilter dataFilter = filter.makeDataFilter((resultId == null ? null : contextInfo.getResults(resultId))); + WebSQLResultsInfo resultsInfo = resultId == null ? null : contextInfo.getResults(resultId); + DBDDataFilter dataFilter = filter.makeDataFilter(resultsInfo); + + DBPPreferenceStore store = dataContainer.getDataSource().getContainer().getPreferenceStore(); + boolean defaultOrderingConfigured = isDefaultOrderingConfigured(store); + if (defaultOrderingConfigured && resultsInfo != null && !dataFilter.hasFilters()) { + applyDefaultOrdering(dataFilter, resultsInfo, store); + } + + AtomicReference resultSetId = new AtomicReference<>(null); DBExecUtils.tryExecuteRecover(monitor, connection.getDataSource(), param -> { try (DBCSession session = executionContext.openSession(monitor, resolveQueryPurpose(dataFilter), "Read data from container")) { try (WebSQLQueryDataReceiver dataReceiver = new WebSQLQueryDataReceiver(contextInfo, dataContainer, dataFormat)) { @@ -349,6 +373,7 @@ public WebSQLExecuteInfo readDataFromContainer( WebSQLQueryResults results = new WebSQLQueryResults(webSession, dataFormat); WebSQLQueryResultSet resultSet = dataReceiver.getResultSet(); + resultSetId.set(resultSet.getId()); results.setResultSet(resultSet); executeInfo.setResults(new WebSQLQueryResults[]{results}); @@ -363,6 +388,11 @@ public WebSQLExecuteInfo readDataFromContainer( } } }); + if (defaultOrderingConfigured && resultId == null && resultSetId.get() != null) { + // Make the second request with initialized resultSet to apply default ordering + return readDataFromContainer(contextInfo, monitor, dataContainer, resultSetId.get(), filter, dataFormat); + } + return executeInfo; } @@ -1253,6 +1283,41 @@ private Object setCellRowValue(Object cellRow, WebSession webSession, DBCSession return convertInputCellValue(dbcSession, allAttributes, cellRow, withoutExecution); } + private boolean isDefaultOrderingConfigured( + @NotNull DBPPreferenceStore store + ) { + OrderingPolicy orderingPolicy = CommonUtils.valueOf( + OrderingPolicy.class, + store.getString(ModelPreferences.RESULT_SET_ORDERING_POLICY), + OrderingPolicy.DEFAULT + ); + return orderingPolicy != OrderingPolicy.DEFAULT; + } + + private void applyDefaultOrdering( + @NotNull DBDDataFilter dataFilter, + @NotNull WebSQLResultsInfo resultsInfo, + @NotNull DBPPreferenceStore store + ) { + OrderingPolicy orderingPolicy = CommonUtils.valueOf( + OrderingPolicy.class, + store.getString(ModelPreferences.RESULT_SET_ORDERING_POLICY), + OrderingPolicy.DEFAULT + ); + DBDRowIdentifier rowIdentifier = resultsInfo.getDefaultRowIdentifier(); + + if (orderingPolicy != OrderingPolicy.DEFAULT && rowIdentifier != null && !rowIdentifier.isIncomplete()) { + List constraints = new ArrayList<>(); + for (DBDAttributeBinding binding : rowIdentifier.getAttributes()) { + DBDAttributeConstraint constraint = new DBDAttributeConstraint(binding); + constraint.setOrderPosition(dataFilter.getMaxOrderingPosition() + 1); + constraint.setOrderDescending(orderingPolicy == OrderingPolicy.PRIMARY_KEY_DESC); + constraints.add(constraint); + } + dataFilter.addConstraints(constraints); + } + } + // TODO: Refactor to unify with desktop when confirmation settings will be added private boolean confirmQueryIfNeeded( @NotNull List scriptElements,