Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions server/bundles/io.cloudbeaver.server/schema/service.sql.graphqls
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,17 @@ input SQLDataFilter {
orderBy: String
}

type SQLResultColumnReference {
"Name of the association (foreign key) that connects this column to the target entity. For reverse references (isReference=true) this is the name of the association in the target entity that points back to this column)"
associationName: String! @since(version: "26.1.0")
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

26.1.1 everywhere

"Fully qualified name of the entity the client will navigate to (referenced entity for forward FK, referencing entity for reverse reference)"
targetEntityName: String @since(version: "26.1.0")
"True if this is a reverse reference (another entity's FK points to this column); false for forward foreign keys"
isReference: Boolean! @since(version: "26.1.0")
"Navigator node path of the target entity, suitable for opening it directly in the UI. Null if the entity is not present in the navigator tree."
nodePath: String @since(version: "26.1.0")
}

type SQLResultColumn {
position: Int!
name: String
Expand All @@ -96,6 +107,9 @@ type SQLResultColumn {
"Operations supported for this attribute"
supportedOperations: [DataTypeLogicalOperation!]!

"Foreign key references for this column"
references: [SQLResultColumnReference!]! @since(version: "26.1.0")

"Description of the column"
description: String @since(version: "25.1.3")
}
Expand Down Expand Up @@ -451,6 +465,19 @@ extend type Mutation {
dataFormat: ResultDataFormat
): AsyncTaskInfo!

"Creates async task for reading referenced data by foreign key cell. Set isReference=true to navigate a reverse reference (other entity's FK pointing to this row)."
asyncSqlNavigateForeignKey(
projectId: ID,
connectionId: ID!,
contextId: ID!,
resultsId: ID!,
columnIndex: Int!,
row: SQLResultRow!,
associationName: String,
dataFormat: ResultDataFormat,
isReference: Boolean @since(version: "26.1.0")
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@SInCE must be after func definition

): AsyncTaskInfo!

"Returns transaction log info for the specified project, connection and context"
getTransactionLogInfo(
projectId: ID!,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,17 @@
@Nullable WebSQLDataFilter filter,
@Nullable WebDataFormat dataFormat) throws DBWebException;

@WebAction
WebAsyncTaskInfo asyncNavigateForeignKey(

Check warning on line 130 in server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/sql/DBWServiceSQL.java

View workflow job for this annotation

GitHub Actions / Server / Lint

[checkstyle] reported by reviewdog 🐶 Reference type 'WebAsyncTaskInfo' is missing a nullability annotation. Raw Output: /github/workspace/./server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/sql/DBWServiceSQL.java:130:5: warning: Reference type 'WebAsyncTaskInfo' is missing a nullability annotation. (sh.adelessfox.checkstyle.checks.NullabilityAnnotationsCheck)
@NotNull WebSession webSession,
@NotNull WebSQLContextInfo contextInfo,
@NotNull String resultsId,
@NotNull Integer columnIndex,
@NotNull WebSQLResultsRow row,
@Nullable String associationName,
@Nullable WebDataFormat dataFormat,
boolean isReference) throws DBException;

/**
* Reads dynamic trace from provided database results.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -480,7 +480,7 @@ public WebSQLExecuteInfo updateResultsDataBatch(

WebSQLQueryResultSet updatedResultSet = new WebSQLQueryResultSet();
updatedResultSet.setResultsInfo(resultsInfo);
updatedResultSet.setColumns(resultsInfo.getAttributes());
updatedResultSet.setColumns(webSession, resultsInfo.getAttributes());

WebSQLQueryResults updateResults = new WebSQLQueryResults(webSession, dataFormat);
updateResults.setUpdateRowCount(totalUpdateCount);
Expand Down Expand Up @@ -652,7 +652,7 @@ private DBSDataManipulator generateUpdateResultsDataBatch(

WebSQLQueryResultSet updatedResultSet = new WebSQLQueryResultSet();
updatedResultSet.setResultsInfo(resultsInfo);
updatedResultSet.setColumns(resultsInfo.getAttributes());
updatedResultSet.setColumns(webSession, resultsInfo.getAttributes());

if (!CommonUtils.isEmpty(updatedRows)) {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ public void fetchEnd(@NotNull DBCSession session, @NotNull DBCResultSet resultSe
}
}

webResultSet.setColumns(bindings);
webResultSet.setColumns(webSession, bindings);
webResultSet.setRows(List.of(rows.toArray(new WebSQLQueryResultSetRow[0])));
webResultSet.setHasChildrenCollection(resultSet instanceof DBDSubCollectionResultSet);
webResultSet.setSupportsDataFilter(dataContainer.isFeatureSupported(DBSDataContainer.FEATURE_DATA_FILTER));
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2024 DBeaver Corp and others
* Copyright (C) 2010-2026 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -16,6 +16,10 @@
*/
package io.cloudbeaver.service.sql;

import io.cloudbeaver.model.session.WebSession;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.model.DBPDataKind;
import org.jkiss.dbeaver.model.DBPEvaluationContext;
Expand All @@ -24,6 +28,13 @@
import org.jkiss.dbeaver.model.exec.DBCLogicalOperator;
import org.jkiss.dbeaver.model.exec.DBExecUtils;
import org.jkiss.dbeaver.model.meta.Property;
import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor;
import org.jkiss.dbeaver.model.runtime.VoidProgressMonitor;
import org.jkiss.dbeaver.model.struct.*;
import org.jkiss.dbeaver.model.virtual.DBVUtils;

import java.util.ArrayList;
import java.util.List;

/**
* Web SQL query resultset.
Expand All @@ -32,9 +43,12 @@

private static final Log log = Log.getLog(WebSQLQueryResultColumn.class);

@Nullable
private final WebSession session;
private final DBDAttributeBinding attrMeta;

public WebSQLQueryResultColumn(DBDAttributeBinding attrMeta) {
public WebSQLQueryResultColumn(@Nullable WebSession session, DBDAttributeBinding attrMeta) {

Check warning on line 50 in server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/sql/WebSQLQueryResultColumn.java

View workflow job for this annotation

GitHub Actions / Server / Lint

[checkstyle] reported by reviewdog 🐶 Reference type 'DBDAttributeBinding' is missing a nullability annotation. Raw Output: /github/workspace/./server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/sql/WebSQLQueryResultColumn.java:50:66: warning: Reference type 'DBDAttributeBinding' is missing a nullability annotation. (sh.adelessfox.checkstyle.checks.NullabilityAnnotationsCheck)
this.session = session;
this.attrMeta = attrMeta;
}

Expand Down Expand Up @@ -130,8 +144,62 @@
return attrMeta.getValueHandler().getSupportedOperators(attrMeta);
}

@Property
public List<WebSQLQueryResultColumnReference> getReferences() {

Check warning on line 148 in server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/sql/WebSQLQueryResultColumn.java

View workflow job for this annotation

GitHub Actions / Server / Lint

[checkstyle] reported by reviewdog 🐶 Reference type 'List' is missing a nullability annotation. Raw Output: /github/workspace/./server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/sql/WebSQLQueryResultColumn.java:148:12: warning: Reference type 'List' is missing a nullability annotation. (sh.adelessfox.checkstyle.checks.NullabilityAnnotationsCheck)
List<WebSQLQueryResultColumnReference> references = new ArrayList<>();
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought we can get refs from model and here just convert into web beans


// Forward references: foreign keys where this column is the source
List<DBSEntityReferrer> referrers = attrMeta.getReferrers();
if (referrers != null) {
for (DBSEntityReferrer referrer : referrers) {
if (referrer instanceof DBSEntityAssociation association) {
references.add(new WebSQLQueryResultColumnReference(session, association, false));
}
}
}

// Reverse references: foreign keys from other entities targeting this column
DBSEntityAttribute entityAttribute = attrMeta.getEntityAttribute();
if (entityAttribute != null) {
DBSEntity parentEntity = entityAttribute.getParentObject();
DBRProgressMonitor monitor = session != null ? session.getProgressMonitor() : new VoidProgressMonitor();
for (DBSEntityAssociation reverseRef : DBVUtils.getAllReferences(monitor, parentEntity)) {
try {
if (referenceTargetsAttribute(monitor, reverseRef, entityAttribute)) {
references.add(new WebSQLQueryResultColumnReference(session, reverseRef, true));
}
} catch (DBException e) {
log.debug("Error reading attributes for reverse reference " + reverseRef.getName(), e);
}
}
}

return references;
}

private boolean referenceTargetsAttribute(
@NotNull DBRProgressMonitor monitor,
@NotNull DBSEntityAssociation association,
@NotNull DBSEntityAttribute attribute
) throws DBException {
DBSEntityConstraint refConstraint = association.getReferencedConstraint();
if (!(refConstraint instanceof DBSEntityReferrer referrer)) {
return false;
}
List<? extends DBSEntityAttributeRef> attrs = referrer.getAttributeReferences(monitor);
if (attrs == null) {
return false;
}
for (DBSEntityAttributeRef ref : attrs) {
if (attribute.equals(ref.getAttribute())) {
return true;
}
}
return false;
}

@Override
public String toString() {

Check warning on line 202 in server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/sql/WebSQLQueryResultColumn.java

View workflow job for this annotation

GitHub Actions / Server / Lint

[checkstyle] reported by reviewdog 🐶 Reference type 'String' is missing a nullability annotation. Raw Output: /github/workspace/./server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/sql/WebSQLQueryResultColumn.java:202:12: warning: Reference type 'String' is missing a nullability annotation. (sh.adelessfox.checkstyle.checks.NullabilityAnnotationsCheck)
return attrMeta.getName();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2026 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.cloudbeaver.service.sql;

import io.cloudbeaver.model.session.WebSession;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.model.DBPEvaluationContext;
import org.jkiss.dbeaver.model.DBUtils;
import org.jkiss.dbeaver.model.meta.Property;
import org.jkiss.dbeaver.model.navigator.DBNNode;
import org.jkiss.dbeaver.model.struct.DBSEntity;
import org.jkiss.dbeaver.model.struct.DBSEntityAssociation;
import org.jkiss.dbeaver.model.struct.DBSEntityConstraint;

/**
* Web SQL query result column reference.
*/
public class WebSQLQueryResultColumnReference {

private static final Log log = Log.getLog(WebSQLQueryResultColumnReference.class);

@Nullable
private final WebSession session;
@NotNull
private final DBSEntityAssociation association;
private final boolean reverse;

public WebSQLQueryResultColumnReference(
@Nullable WebSession session,
@NotNull DBSEntityAssociation association,
boolean reverse
) {
this.session = session;
this.association = association;
this.reverse = reverse;
}

@NotNull
@Property
public String getAssociationName() {
return association.getName();
}

@Property
public boolean isReference() {
return reverse;
}

@Nullable
@Property
public String getTargetEntityName() {
DBSEntity targetEntity = getTargetEntity();
if (targetEntity == null) {
return null;
}
return DBUtils.getObjectFullName(targetEntity, DBPEvaluationContext.UI);
}

@Nullable
@Property
public String getNodePath() {
if (session == null) {
return null;
}
DBSEntity targetEntity = getTargetEntity();
if (targetEntity == null) {
return null;
}
try {
DBNNode node = session.getNavigatorModelOrThrow()
.getNodeByObject(session.getProgressMonitor(), targetEntity, false);
return node == null ? null : node.getNodeUri();
} catch (DBException e) {
log.debug("Error resolving navigator node for entity " + targetEntity.getName(), e);
return null;
}
}

@Nullable
private DBSEntity getTargetEntity() {
if (reverse) {
return association.getParentObject();
}
DBSEntityConstraint referencedConstraint = association.getReferencedConstraint();
if (referencedConstraint == null) {
return null;
}
return referencedConstraint.getParentObject();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
*/
package io.cloudbeaver.service.sql;

import io.cloudbeaver.model.session.WebSession;
import io.cloudbeaver.service.sql.WebSQLResultSetRowIdentifier.WebSQLResultSetRowIdentifierState;
import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
Expand Down Expand Up @@ -67,10 +68,10 @@
this.columns = columns;
}

public void setColumns(DBDAttributeBinding[] bindings) {
public void setColumns(@Nullable WebSession session, DBDAttributeBinding[] bindings) {

Check warning on line 71 in server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/sql/WebSQLQueryResultSet.java

View workflow job for this annotation

GitHub Actions / Server / Lint

[checkstyle] reported by reviewdog 🐶 Reference type 'DBDAttributeBinding[]' is missing a nullability annotation. Raw Output: /github/workspace/./server/bundles/io.cloudbeaver.server/src/io/cloudbeaver/service/sql/WebSQLQueryResultSet.java:71:58: warning: Reference type 'DBDAttributeBinding[]' is missing a nullability annotation. (sh.adelessfox.checkstyle.checks.NullabilityAnnotationsCheck)
WebSQLQueryResultColumn[] columns = new WebSQLQueryResultColumn[bindings.length];
for (int i = 0; i < bindings.length; i++) {
columns[i] = new WebSQLQueryResultColumn(bindings[i]);
columns[i] = new WebSQLQueryResultColumn(session, bindings[i]);
}
this.columns = columns;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,17 @@ public void bindWiring(DBWBindingContext model) throws DBWebException {
getDataFilter(env),
getDataFormat(env)
))
.dataFetcher("asyncSqlNavigateForeignKey", env ->
getService(env).asyncNavigateForeignKey(
getWebSession(env),
getSQLContext(env),
getArgumentVal(env, "resultsId"),
getArgumentVal(env, "columnIndex"),
new WebSQLResultsRow(getArgument(env, "row")),
getArgument(env, "associationName"),
getDataFormat(env),
CommonUtils.toBoolean(env.getArgument("isReference"))
))
.dataFetcher("asyncSqlExecuteResults", env ->
getService(env).asyncGetQueryResults(
getWebSession(env), getArgumentVal(env, "taskId")
Expand Down
Loading
Loading