CB-4829 connection cache same ids fix (#2491)

* CB-4829 connection cache same ids fix

* CB-4829 find connection info with project id fix

* CB-4829 add project id to gql functions

---------

Co-authored-by: kseniaguzeeva <112612526+kseniaguzeeva@users.noreply.github.com>
This commit is contained in:
Ainur
2024-04-02 16:47:48 +00:00
committed by GitHub
co-authored by kseniaguzeeva
parent 5fd847f209
commit 8b5aac0093
23 changed files with 91 additions and 45 deletions
@@ -34,8 +34,6 @@ import io.cloudbeaver.utils.CBModelConstants;
import io.cloudbeaver.utils.WebAppUtils;
import io.cloudbeaver.utils.WebDataSourceUtils;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
@@ -284,7 +282,7 @@ public class WebSession extends BaseWebSession
switch (type) {
case DATASOURCE_CREATED -> {
WebConnectionInfo connectionInfo = new WebConnectionInfo(this, ds);
this.connections.put(connectionInfo.getId(), connectionInfo);
this.connections.put(getConnectionId(ds), connectionInfo);
sendDataSourceUpdatedEvent = true;
}
case DATASOURCE_UPDATED -> // if settings were changed we need to send event
@@ -294,7 +292,7 @@ public class WebSession extends BaseWebSession
if (registry instanceof DBPDataSourceRegistryCache dsrc) {
dsrc.removeDataSourceFromList(ds);
}
this.connections.remove(ds.getId());
this.connections.remove(getConnectionId(ds));
sendDataSourceUpdatedEvent = true;
}
default -> {
@@ -304,6 +302,16 @@ public class WebSession extends BaseWebSession
return sendDataSourceUpdatedEvent;
}
@NotNull
private String getConnectionId(@NotNull DBPDataSourceContainer container) {
return getConnectionId(container.getProject().getId(), container.getId());
}
@NotNull
private String getConnectionId(@NotNull String projectId, @NotNull String dsId) {
return projectId + ":" + dsId;
}
// Note: for admin use only
public synchronized void resetUserState() throws DBException {
clearAuthTokens();
@@ -474,7 +482,7 @@ public class WebSession extends BaseWebSession
var registry = getProjectById(WebAppUtils.getGlobalProjectId()).getDataSourceRegistry();
var dataSource = registry.getDataSource(dsId);
if (dataSource != null) {
connections.put(dsId, new WebConnectionInfo(this, dataSource));
connections.put(getConnectionId(dataSource), new WebConnectionInfo(this, dataSource));
// reflect changes is navigator model
registry.notifyDataSourceListeners(new DBPEvent(DBPEvent.Action.OBJECT_ADD, dataSource, true));
}
@@ -485,7 +493,7 @@ public class WebSession extends BaseWebSession
var dataSource = registry.getDataSource(dsId);
if (dataSource != null) {
this.accessibleConnectionIds.remove(dsId);
connections.remove(dsId);
connections.remove(getConnectionId(dataSource));
// reflect changes is navigator model
registry.notifyDataSourceListeners(new DBPEvent(DBPEvent.Action.OBJECT_REMOVE, dataSource));
dataSource.dispose();
@@ -564,9 +572,21 @@ public class WebSession extends BaseWebSession
@NotNull
public WebConnectionInfo getWebConnectionInfo(@Nullable String projectId, String connectionID) throws DBWebException {
WebConnectionInfo connectionInfo;
WebConnectionInfo connectionInfo = null;
synchronized (connections) {
connectionInfo = connections.get(connectionID);
if (projectId != null) {
connectionInfo = connections.get(getConnectionId(projectId, connectionID));
} else {
addWarningMessage("Project id is not defined in request. Try to find it from connection cache");
for (Map.Entry<String, WebConnectionInfo> entry : connections.entrySet()) {
String k = entry.getKey();
WebConnectionInfo v = entry.getValue();
if (k.contains(connectionID)) {
connectionInfo = v;
break;
}
}
}
}
if (connectionInfo == null) {
WebProjectImpl project = getProjectById(projectId);
@@ -577,7 +597,7 @@ public class WebSession extends BaseWebSession
if (dataSource != null) {
connectionInfo = new WebConnectionInfo(this, dataSource);
synchronized (connections) {
connections.put(connectionID, connectionInfo);
connections.put(getConnectionId(dataSource), connectionInfo);
}
} else {
throw new DBWebException("Connection '" + connectionID + "' not found");
@@ -587,22 +607,22 @@ public class WebSession extends BaseWebSession
}
@Nullable
public WebConnectionInfo findWebConnectionInfo(String connectionID) {
public WebConnectionInfo findWebConnectionInfo(String projectId, String connectionId) {
synchronized (connections) {
return connections.get(connectionID);
return connections.get(getConnectionId(projectId, connectionId));
}
}
public void addConnection(WebConnectionInfo connectionInfo) {
synchronized (connections) {
connections.put(connectionInfo.getId(), connectionInfo);
connections.put(getConnectionId(connectionInfo.getDataSourceContainer()), connectionInfo);
}
}
public void removeConnection(WebConnectionInfo connectionInfo) {
connectionInfo.clearCache();
synchronized (connections) {
connections.remove(connectionInfo.getId());
connections.remove(getConnectionId(connectionInfo.getDataSourceContainer()));
}
}
@@ -929,7 +949,7 @@ public class WebSession extends BaseWebSession
}
configuration.setRuntimeAttribute(RUNTIME_PARAM_AUTH_INFOS, getAllAuthInfo());
WebConnectionInfo webConnectionInfo = findWebConnectionInfo(dataSourceContainer.getId());
WebConnectionInfo webConnectionInfo = findWebConnectionInfo(dataSourceContainer.getProject().getId(), dataSourceContainer.getId());
if (webConnectionInfo != null) {
WebDataSourceUtils.saveCredentialsInDataSource(webConnectionInfo, dataSourceContainer, configuration);
}
@@ -81,7 +81,7 @@ public class WSObjectPermissionUpdatedEventHandler extends WSDefaultEventHandler
return;
}
if (WSEventType.OBJECT_PERMISSIONS_UPDATED.getEventId().equals(event.getId())) {
isAccessibleNow = webSession.findWebConnectionInfo(objectId) != null;
isAccessibleNow = webSession.findWebConnectionInfo(project.getId(), objectId) != null;
if (isAccessibleNow) {
return;
}
@@ -40,7 +40,7 @@ public class WSUserSecretEventHandlerImpl extends WSDefaultEventHandler<WSUserSe
activeUserSession.addSessionEvent(event);
return;
}
var connectionInfo = ((WebSession) activeUserSession).findWebConnectionInfo(event.getDataSourceId());
var connectionInfo = ((WebSession) activeUserSession).findWebConnectionInfo(event.getProjectId(), event.getDataSourceId());
if (connectionInfo == null) {
log.debug("Connection " + event.getDataSourceId() + " is not found in session " + activeUserSession.getSessionId());
return;
@@ -31,6 +31,7 @@ import java.io.IOException;
public class LocalServletHandler extends AbstractActionServletHandler {
public static final String URI_PREFIX = "open";
public static final String PARAM_PROJECT_ID = "project_id";
public static final String PARAM_CONNECTION_ID = "id";
public static final String PARAM_CONNECTION_NAME = "name";
public static final String PARAM_CONNECTION_URL = "url";
@@ -25,9 +25,7 @@ import io.cloudbeaver.server.actions.CBServerAction;
import org.jkiss.dbeaver.DBException;
import java.io.IOException;
import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* LocalSessionHandler
@@ -52,29 +50,22 @@ public class LocalSessionHandler extends AbstractActionSessionHandler {
@Override
protected void openDatabaseConsole(WebSession webSession, CBServerAction action) throws DBException {
String projectId = action.getParameter(LocalServletHandler.PARAM_PROJECT_ID);
String connectionId = action.getParameter(LocalServletHandler.PARAM_CONNECTION_ID);
String connectionName = action.getParameter(LocalServletHandler.PARAM_CONNECTION_NAME);
String connectionURL = action.getParameter(LocalServletHandler.PARAM_CONNECTION_URL);
WebConnectionInfo connectionInfo = null;
Stream<WebConnectionInfo> stream = webSession.getConnections().stream();
if (projectId != null) {
stream = stream.filter(c -> c.getProjectId().equals(projectId));
}
if (connectionId != null) {
connectionInfo = webSession.findWebConnectionInfo(connectionId);
stream = stream.filter(c -> c.getId().equals(connectionId));
} else if (connectionName != null) {
connectionInfo = findConnection(webSession, t -> t.getName().equals(connectionName));
stream = stream.filter(t -> t.getName().equals(connectionName));
} else if (connectionURL != null) {
connectionInfo = findConnection(webSession, t -> t.getUrl().equals(connectionURL));
}
if (connectionInfo == null) {
throw new DBException("Connection is not found in the session");
stream = stream.filter(t -> t.getUrl().equals(connectionURL));
}
WebConnectionInfo connectionInfo = stream.findFirst().orElseThrow(() -> new DBException("Connection is not found in the session"));
WebServiceUtils.fireActionParametersOpenEditor(webSession, connectionInfo.getDataSourceContainer(), false);
}
private WebConnectionInfo findConnection(WebSession webSession, Predicate<WebConnectionInfo> filter) {
List<WebConnectionInfo> filteredConnections = webSession.getConnections().stream().filter(filter).collect(Collectors.toList());
if (filteredConnections.size() != 1) {
return null;
}
return filteredConnections.get(0);
}
}
@@ -44,6 +44,7 @@ export class ConnectionDialectResource extends CachedMapResource<IConnectionInfo
async formatScript(context: IConnectionExecutionContextInfo, query: string): Promise<string> {
const result = await this.graphQLService.sdk.formatSqlQuery({
projectId: context.projectId,
connectionId: context.connectionId,
contextId: context.id,
query,
@@ -1,5 +1,6 @@
mutation asyncReadDataFromContainer(
$projectId: ID!
$connectionId: ID!
$contextId: ID!
$containerNodePath: ID!
@@ -8,6 +9,7 @@ mutation asyncReadDataFromContainer(
$dataFormat: ResultDataFormat
) {
taskInfo: asyncReadDataFromContainer(
projectId: $projectId
connectionId: $connectionId
contextId: $contextId
containerNodePath: $containerNodePath
@@ -1,4 +1,5 @@
mutation asyncSqlExecuteQuery(
$projectId: ID!
$connectionId: ID!
$contextId: ID!
$query: String!
@@ -8,6 +9,7 @@ mutation asyncSqlExecuteQuery(
$readLogs: Boolean
) {
taskInfo: asyncSqlExecuteQuery(
projectId: $projectId
connectionId: $connectionId
contextId: $contextId
sql: $query
@@ -1,10 +1,12 @@
mutation asyncSqlExplainExecutionPlan(
$projectId: ID!
$connectionId: ID!
$contextId: ID!
$query: String!
$configuration: Object!
) {
taskInfo: asyncSqlExplainExecutionPlan(
projectId: $projectId
connectionId: $connectionId
contextId: $contextId
query: $query
@@ -1,9 +1,11 @@
mutation closeResult(
$projectId: ID!
$connectionId: ID!
$contextId: ID!
$resultId: ID!
) {
result: sqlResultClose(
projectId: $projectId
connectionId: $connectionId
contextId: $contextId
resultId: $resultId
@@ -1,3 +1,3 @@
mutation getResultsetDataURL($connectionId: ID!, $contextId: ID!, $resultsId: ID!, $lobColumnIndex: Int!, $row: SQLResultRow!) {
url: sqlReadLobValue(connectionId: $connectionId, contextId: $contextId, resultsId: $resultsId, lobColumnIndex: $lobColumnIndex, row: $row)
mutation getResultsetDataURL($projectId: ID!, $connectionId: ID!, $contextId: ID!, $resultsId: ID!, $lobColumnIndex: Int!, $row: SQLResultRow!) {
url: sqlReadLobValue(projectId: $projectId, connectionId: $connectionId, contextId: $contextId, resultsId: $resultsId, lobColumnIndex: $lobColumnIndex, row: $row)
}
@@ -1,9 +1,11 @@
query formatSqlQuery(
$projectId: ID!
$connectionId: ID!
$contextId: ID!
$query: String!
) {
query: sqlFormatQuery(
projectId: $projectId
connectionId: $connectionId
contextId: $contextId
query: $query
@@ -1,9 +1,11 @@
query parseSQLQuery(
$projectId: ID!
$connectionId: ID!
$script: String!
$position: Int!
) {
queryInfo: sqlParseQuery(
projectId: $projectId
connectionId: $connectionId
script: $script
position: $position
@@ -1,8 +1,10 @@
query parseSQLScript(
$projectId: ID!
$connectionId: ID!
$script: String!
) {
scriptInfo: sqlParseScript(
projectId: $projectId
connectionId: $connectionId
script: $script
) {
@@ -1,4 +1,5 @@
query querySqlCompletionProposals(
$projectId : ID!
$connectionId: ID!
$contextId: ID!
$position: Int!
@@ -7,6 +8,7 @@ query querySqlCompletionProposals(
$maxResults: Int
) {
proposals: sqlCompletionProposals(
projectId: $projectId
connectionId: $connectionId
contextId: $contextId
query: $query
@@ -1,3 +1,3 @@
mutation sqlReadStringValue($connectionId: ID!, $contextId: ID!, $resultsId: ID!, $columnIndex: Int!, $row: SQLResultRow!) {
text: sqlReadStringValue(connectionId: $connectionId, contextId: $contextId, resultsId: $resultsId, columnIndex: $columnIndex, row: $row)
mutation sqlReadStringValue($projectId: ID!, $connectionId: ID!, $contextId: ID!, $resultsId: ID!, $columnIndex: Int!, $row: SQLResultRow!) {
text: sqlReadStringValue(projectId: $projectId, connectionId: $connectionId, contextId: $contextId, resultsId: $resultsId, columnIndex: $columnIndex, row: $row)
}
@@ -98,6 +98,7 @@ export class ContainerDataSource extends ResultSetDataSource<IDataContainerOptio
const task = this.asyncTaskInfoService.create(async () => {
const { taskInfo } = await this.graphQLService.sdk.asyncReadDataFromContainer({
projectId: context.projectId,
connectionId: context.connectionId,
contextId: context.id,
containerNodePath: options.containerNodePath,
@@ -237,6 +238,7 @@ export class ContainerDataSource extends ResultSetDataSource<IDataContainerOptio
}
try {
await this.graphQLService.sdk.closeResult({
projectId: result.projectId,
connectionId: result.connectionId,
contextId: result.contextId,
resultId: result.id,
@@ -251,6 +253,7 @@ export class ContainerDataSource extends ResultSetDataSource<IDataContainerOptio
return results.map<IDatabaseResultSet>((result, index) => ({
id: result.resultSet?.id || '0',
uniqueResultId: `${executionContextInfo.connectionId}_${executionContextInfo.id}_${index}`,
projectId: executionContextInfo.projectId,
connectionId: executionContextInfo.connectionId,
contextId: executionContextInfo.id,
dataFormat: result.dataFormat!,
@@ -194,6 +194,7 @@ export class ResultSetDataContentAction extends DatabaseDataAction<any, IDatabas
const response = await this.graphQLService.sdk.sqlReadStringValue({
resultsId: result.id,
projectId: result.projectId,
connectionId: result.connectionId,
contextId: result.contextId,
columnIndex,
@@ -225,6 +226,7 @@ export class ResultSetDataContentAction extends DatabaseDataAction<any, IDatabas
const { url } = await this.graphQLService.sdk.getResultsetDataURL({
resultsId: result.id,
projectId: result.projectId,
connectionId: result.connectionId,
contextId: result.contextId,
lobColumnIndex: columnIndex,
@@ -10,6 +10,7 @@ import type { ResultDataFormat } from '@cloudbeaver/core-sdk';
export interface IDatabaseDataResult {
id: string | null;
uniqueResultId: string;
projectId: string;
connectionId: string;
contextId: string;
dataFormat: ResultDataFormat;
@@ -204,6 +204,7 @@ export class QueryDataSource<TOptions extends IDataQueryOptions = IDataQueryOpti
const task = this.asyncTaskInfoService.create(async () => {
const { taskInfo } = await this.graphQLService.sdk.asyncSqlExecuteQuery({
projectId: executionContextInfo.projectId,
connectionId: executionContextInfo.connectionId,
contextId: executionContextInfo.id,
query: options.query,
@@ -262,6 +263,7 @@ export class QueryDataSource<TOptions extends IDataQueryOptions = IDataQueryOpti
}
try {
await this.graphQLService.sdk.closeResult({
projectId: result.projectId,
connectionId: result.connectionId,
contextId: result.contextId,
resultId: result.id,
@@ -276,6 +278,7 @@ export class QueryDataSource<TOptions extends IDataQueryOptions = IDataQueryOpti
return results.map<IDatabaseResultSet>((result, index) => ({
id: result.resultSet?.id || null,
uniqueResultId: `${executionContextInfo.connectionId}_${executionContextInfo.id}_${index}`,
projectId: executionContextInfo.projectId,
connectionId: executionContextInfo.connectionId,
contextId: executionContextInfo.id,
dataFormat: result.dataFormat!,
@@ -193,6 +193,7 @@ export function useSqlEditor(state: ISqlEditorTabState): ISQLEditorData {
}
const hints = await this.sqlEditorService.getAutocomplete(
executionContext.projectId,
executionContext.connectionId,
executionContext.id,
this.value,
@@ -369,10 +370,11 @@ export function useSqlEditor(state: ISqlEditorTabState): ISQLEditorData {
if (!this.dataSource?.hasFeature(ESqlDataSourceFeatures.script)) {
return;
}
const projectId = this.dataSource.executionContext?.projectId;
const connectionId = this.dataSource.executionContext?.connectionId;
const script = this.parser.actualScript;
if (!connectionId || !script) {
if (!projectId || !connectionId || !script) {
this.parser.setQueries([]);
this.onUpdate.execute();
return;
@@ -380,7 +382,7 @@ export function useSqlEditor(state: ISqlEditorTabState): ISQLEditorData {
const { queries } = await this.parseScript([connectionId, script], async () => {
try {
return await this.sqlEditorService.parseSQLScript(connectionId, script);
return await this.sqlEditorService.parseSQLScript(projectId, connectionId, script);
} catch (exception: any) {
this.notificationService.logException(exception, 'Failed to parse SQL script');
throw exception;
@@ -428,9 +430,10 @@ export function useSqlEditor(state: ISqlEditorTabState): ISQLEditorData {
},
async getResolvedSegment(): Promise<ISQLScriptSegment | undefined> {
const projectId = this.dataSource?.executionContext?.projectId;
const connectionId = this.dataSource?.executionContext?.connectionId;
if (!connectionId || this.cursor.begin !== this.cursor.end) {
if (!projectId || !connectionId || this.cursor.begin !== this.cursor.end) {
return this.getSubQuery();
}
@@ -438,7 +441,7 @@ export function useSqlEditor(state: ISqlEditorTabState): ISQLEditorData {
return this.activeSegment;
}
const result = await this.sqlEditorService.parseSQLQuery(connectionId, this.value, this.cursor.begin);
const result = await this.sqlEditorService.parseSQLQuery(projectId, connectionId, this.value, this.cursor.begin);
const segment = this.parser.getSegment(result.start, result.end);
@@ -72,8 +72,9 @@ export class SqlEditorService {
});
}
async parseSQLScript(connectionId: string, script: string): Promise<SqlScriptInfoFragment> {
async parseSQLScript(projectId: string, connectionId: string, script: string): Promise<SqlScriptInfoFragment> {
const result = await this.graphQLService.sdk.parseSQLScript({
projectId,
connectionId,
script,
});
@@ -81,8 +82,9 @@ export class SqlEditorService {
return result.scriptInfo;
}
async parseSQLQuery(connectionId: string, script: string, position: number) {
async parseSQLQuery(projectId: string, connectionId: string, script: string, position: number) {
const result = await this.graphQLService.sdk.parseSQLQuery({
projectId,
connectionId,
script,
position,
@@ -92,6 +94,7 @@ export class SqlEditorService {
}
async getAutocomplete(
projectId: string,
connectionId: string,
contextId: string,
query: string,
@@ -100,6 +103,7 @@ export class SqlEditorService {
simple?: boolean,
): Promise<SQLProposal[]> {
const { proposals } = await this.graphQLService.sdk.querySqlCompletionProposals({
projectId,
connectionId,
contextId,
query,
@@ -56,6 +56,7 @@ export class SqlExecutionPlanService {
const asyncTask = this.asyncTaskInfoService.create(async () => {
const { taskInfo } = await this.graphQLService.sdk.asyncSqlExplainExecutionPlan({
projectId: contextInfo.projectId,
connectionId: contextInfo.connectionId,
contextId: contextInfo.id,
query,