Cb 4457 subcollections firestore (#2338)

* CB-4457. Add api for read subcollections firestore

* CB-4457. Move firestore to ee module

* CB-4457. Move firestore to ee module

* CB-4457. Added annotations for firestore

* CB-4457. Added metaData

* CB-4457. Fixed build

* CB-4457 add hasChildrenCollection flag

* CB-4457. Add new resultsRow

* CB-4457. Renamed SQLResultRowMetaData parameters

* CB-4457 add rows with metadata to schema

* CB-4457. Fix gql

* CB-4457. Added logic to get Firestore subcollection

* CB-4457. Refactor result, rename gql, added new type to return gql

* CB-4457. Revert list to map

* CB-4457. Get Subcollections info

* CB-4457. Refactor code

* Added sceleton for insert subcollection

* CB-4457. Added isSupportsDataFilter flag for subcollection

* CB-4457 add locales

* CB-4457. Added isSupportsDataFilter flag for subcollection

* CB-4457. Added update/insert logic for subcollection

* CB-4457 support collection editing

* CB-4457. Refactor after review, moved hierarchical model to ee.

* CB-4457 add SerializableKey

* CB-4457 keep only new rowsWithMetaData api

* CB-4457 review fixes

* CB-4457 review fixes

* CB-4457. Refactor after review. Added logic for delete

* CB-4457 pass metadata in deleted rows

* CB-4457 check row for undefined

* CB-4457 pass metadata when editing json

* CB-4457. Refactor edit, fix icons

* CB-4457. Refactor makeDocumentInputValue, created new method for locator

* CB-4457 try to get the current metadata for row

* CB-4457 clear cache on result update

* CB-4457 remove extra check

* CB-4457. Fixed npe

---------

Co-authored-by: DenisSinelnikov <denis.sinelnikov@dbaever.com>
Co-authored-by: naumov <iamemptyhuh@gmail.com>
Co-authored-by: Aleksei Potsetsuev <wrouds@gmail.com>
Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com>
This commit is contained in:
DenisSinelnikov
2024-02-13 18:29:22 +01:00
committed by GitHub
co-authored by DenisSinelnikov naumov Aleksei Potsetsuev Daria Marutkina
parent eea72bd467
commit cb897343bd
26 changed files with 495 additions and 158 deletions
@@ -93,6 +93,11 @@ type SQLResultColumn {
supportedOperations: [DataTypeLogicalOperation!]!
}
type SQLResultRowMetaData {
data: [Object]!
metaData: Object
}
type DatabaseDocument {
id: String
contentType: String
@@ -103,7 +108,8 @@ type DatabaseDocument {
type SQLResultSet {
id: ID!
columns: [ SQLResultColumn ]
rows: [ [ Object ] ]
rows: [ [ Object ] ] @deprecated
rowsWithMetaData: [SQLResultRowMetaData]
# True means that resultset was generated by single entity query
# New rows can be added, old rows can be deleted
@@ -112,6 +118,8 @@ type SQLResultSet {
hasMoreData: Boolean!
# can't update data or load LOB file if hasRowIdentifier = false
hasRowIdentifier: Boolean!
hasChildrenCollection: Boolean!
isSupportsDataFilter: Boolean!
}
type SQLQueryResults {
@@ -144,6 +152,12 @@ type SQLExecuteInfo {
input SQLResultRow {
data: [ Object ]!
updateValues: Object
metaData: Object
}
input SQLResultRowMetaDataInput {
data: [Object]
metaData: Object!
}
type DataTypeLogicalOperation {
@@ -386,4 +400,5 @@ extend type Mutation {
): AsyncTaskInfo!
asyncSqlRowDataCountResult(taskId: ID!): Int!
}
@@ -331,7 +331,7 @@ public class WebSQLProcessor implements WebSessionProvider {
{
// we don't need to add same row several times
// (it can be when we update the row from RS with several tables)
Set<Object[]> newResultSetRows = new LinkedHashSet<>();
Set<WebSQLQueryResultSetRow> newResultSetRows = new LinkedHashSet<>();
KeyDataReceiver keyReceiver = new KeyDataReceiver(contextInfo.getResults(resultsId));
WebSQLResultsInfo resultsInfo = contextInfo.getResults(resultsId);
@@ -372,7 +372,7 @@ public class WebSQLProcessor implements WebSessionProvider {
totalUpdateCount += statistics.getRowsUpdated();
result.setDuration(result.getDuration() + statistics.getExecuteTime());
newResultSetRows.add(rowValues);
newResultSetRows.add(new WebSQLQueryResultSetRow(rowValues, null));
}
if (txnManager != null && txnManager.isSupportsTransactions()) {
@@ -399,7 +399,7 @@ public class WebSQLProcessor implements WebSessionProvider {
WebSQLQueryResults updateResults = new WebSQLQueryResults(webSession, dataFormat);
updateResults.setUpdateRowCount(totalUpdateCount);
updateResults.setResultSet(updatedResultSet);
updatedResultSet.setRows(newResultSetRows.toArray(new Object[0][]));
updatedResultSet.setRows(List.of(newResultSetRows.toArray(new WebSQLQueryResultSetRow[0])));
queryResults.add(updateResults);
@@ -410,7 +410,7 @@ public class WebSQLProcessor implements WebSessionProvider {
private void getUpdatedRowsInfo(
@NotNull WebSQLResultsInfo resultsInfo,
@NotNull Set<Object[]> newResultSetRows,
@NotNull Set<WebSQLQueryResultSetRow> newResultSetRows,
@Nullable WebDataFormat dataFormat,
@NotNull DBRProgressMonitor monitor)
throws DBCException {
@@ -420,8 +420,8 @@ public class WebSQLProcessor implements WebSessionProvider {
"Refresh row(s) after insert/update")
) {
boolean canRefreshResults = resultsInfo.canRefreshResults();
for (Object[] row : newResultSetRows) {
if (row.length == 0) {
for (WebSQLQueryResultSetRow row : newResultSetRows) {
if (row.getData().length == 0) {
continue;
}
if (!canRefreshResults) {
@@ -438,7 +438,7 @@ public class WebSQLProcessor implements WebSessionProvider {
if (attr.getRowIdentifier() == null) {
continue;
}
final Object keyValue = row[attr.getOrdinalPosition()];
final Object keyValue = row.getData()[attr.getOrdinalPosition()];
if (DBUtils.isNullValue(keyValue)) {
hasKey = false;
break;
@@ -455,7 +455,7 @@ public class WebSQLProcessor implements WebSessionProvider {
}
DBDDataFilter filter = new DBDDataFilter(constraints);
DBSDataContainer dataContainer = resultsInfo.getDataContainer();
WebRowDataReceiver dataReceiver = new WebRowDataReceiver(resultsInfo.getAttributes(), row, dataFormat);
WebRowDataReceiver dataReceiver = new WebRowDataReceiver(resultsInfo.getAttributes(), row.getData(), dataFormat);
dataContainer.readData(
new AbstractExecutionSource(dataContainer, getExecutionContext(dataContainer), this),
session,
@@ -471,14 +471,14 @@ public class WebSQLProcessor implements WebSessionProvider {
private void makeWebCellRow(
@NotNull WebSQLResultsInfo resultsInfo,
@NotNull Object[] row,
@NotNull WebSQLQueryResultSetRow row,
@Nullable WebDataFormat dataFormat
) throws DBCException {
for (int i = 0; i < row.length; i++) {
row[i] = WebSQLUtils.makeWebCellValue(
for (int i = 0; i < row.getData().length; i++) {
row.getData()[i] = WebSQLUtils.makeWebCellValue(
webSession,
resultsInfo.getAttributeByPosition(i),
row[i],
row.getData()[i],
dataFormat);
}
}
@@ -563,6 +563,14 @@ public class WebSQLProcessor implements WebSessionProvider {
Map<String, Object> updateValues = row.getUpdateValues().entrySet().stream()
.filter(x -> CommonUtils.equalObjects(allAttributes[CommonUtils.toInt(x.getKey())].getRowIdentifier(), rowIdentifier))
.collect(HashMap::new, (m,v) -> m.put(v.getKey(), v.getValue()), HashMap::putAll);
Map<String, Object> metaData;
if (row.getMetaData() != null) {
metaData = new HashMap<>(row.getMetaData());
} else {
metaData = new HashMap<>();
}
if (finalRow.length == 0 || CommonUtils.isEmpty(updateValues)) {
continue;
}
@@ -582,7 +590,7 @@ public class WebSQLProcessor implements WebSessionProvider {
boolean isDocumentValue = keyAttributes.length == 1 && keyAttribute.getDataKind() == DBPDataKind.DOCUMENT && dataContainer instanceof DBSDocumentLocator;
if (isDocumentValue) {
rowValues[updateAttributes.length + i] =
makeDocumentInputValue(session, (DBSDocumentLocator) dataContainer, resultsInfo, row);
makeDocumentInputValue(session, (DBSDocumentLocator) dataContainer, resultsInfo, row, metaData);
} else {
rowValues[updateAttributes.length + i] = keyAttribute.getValueHandler().getValueFromObject(
session,
@@ -651,6 +659,7 @@ public class WebSQLProcessor implements WebSessionProvider {
if (keyAttributes.length > 0 && !CommonUtils.isEmpty(deletedRows)) {
for (WebSQLResultsRow row : deletedRows) {
Object[] keyData = row.getData();
Map<String, Object> keyMetaData = row.getMetaData();
if (keyData.length == 0) {
continue;
}
@@ -658,20 +667,37 @@ public class WebSQLProcessor implements WebSessionProvider {
boolean isDocumentKey = keyAttributes.length == 1 && keyAttributes[0].getDataKind() == DBPDataKind.DOCUMENT;
for (int i = 0; i < allAttributes.length; i++) {
if (isDocumentKey || ArrayUtils.contains(keyAttributes, allAttributes[i])) {
Object realCellValue = convertInputCellValue(session, allAttributes[i],
keyData[i], withoutExecution);
delKeyAttributes.put(allAttributes[i], realCellValue);
if (dataContainer instanceof DBSDocumentLocator dataLocator) {
Map<String, Object> keyMap = new LinkedHashMap<>();
DBDAttributeBinding[] attributes = resultsInfo.getAttributes();
for (int j = 0; j < attributes.length; j++) {
DBDAttributeBinding attr = attributes[j];
Object plainValue = WebSQLUtils.makePlainCellValue(session, attr, row.getData()[j]);
keyMap.put(attr.getName(), plainValue);
}
}
DBDDocument document = dataLocator.findDocument(session, keyMap, keyMetaData);
DBSDataManipulator.ExecuteBatch deleteBatch = dataManipulator.deleteData(
session,
delKeyAttributes.keySet().toArray(new DBSAttributeBase[0]),
executionSource);
deleteBatch.add(delKeyAttributes.values().toArray());
resultBatches.put(deleteBatch, new Object[0]);
DBSDataManipulator.ExecuteBatch deleteBatch = dataManipulator.deleteData(
session,
keyAttributes,
executionSource);
deleteBatch.add(new Object[] {document});
resultBatches.put(deleteBatch, new Object[0]);
} else {
for (int i = 0; i < allAttributes.length; i++) {
if (isDocumentKey || ArrayUtils.contains(keyAttributes, allAttributes[i])) {
Object realCellValue = convertInputCellValue(session, allAttributes[i],
keyData[i], withoutExecution);
delKeyAttributes.put(allAttributes[i], realCellValue);
}
}
DBSDataManipulator.ExecuteBatch deleteBatch = dataManipulator.deleteData(
session,
delKeyAttributes.keySet().toArray(new DBSAttributeBase[0]),
executionSource);
deleteBatch.add(delKeyAttributes.values().toArray());
resultBatches.put(deleteBatch, new Object[0]);
}
}
}
}
@@ -693,7 +719,8 @@ public class WebSQLProcessor implements WebSessionProvider {
DBCSession session,
DBSDocumentLocator dataContainer,
WebSQLResultsInfo resultsInfo,
WebSQLResultsRow row) throws DBException
WebSQLResultsRow row,
Map<String, Object> metaData) throws DBException
{
// Document reference
DBDDocument document = null;
@@ -702,16 +729,18 @@ public class WebSQLProcessor implements WebSessionProvider {
for (int j = 0; j < attributes.length; j++) {
DBDAttributeBinding attr = attributes[j];
Object plainValue = WebSQLUtils.makePlainCellValue(session, attr, row.getData()[j]);
if (plainValue instanceof DBDDocument) {
if (plainValue instanceof DBDDocument dbdDocument) {
// FIXME: Hack for DynamoDB. We pass entire document as a key
// FIXME: Let's just return it back for now
document = (DBDDocument) plainValue;
break;
if (dataContainer.isDocumentValid(dbdDocument)) {
document = (DBDDocument) plainValue;
break;
}
}
keyMap.put(attr.getName(), plainValue);
}
if (document == null) {
document = dataContainer.findDocument(session.getProgressMonitor(), keyMap);
document = dataContainer.findDocument(session, keyMap, metaData);
if (document == null) {
throw new DBCException("Error finding document by key " + keyMap);
}
@@ -826,7 +855,7 @@ public class WebSQLProcessor implements WebSessionProvider {
boolean isDocumentValue = keyAttributes.length == 1 && keyAttribute.getDataKind() == DBPDataKind.DOCUMENT && dataContainer instanceof DBSDocumentLocator;
if (isDocumentValue) {
rowValues[i] =
makeDocumentInputValue(session, (DBSDocumentLocator) dataContainer, resultsInfo, row);
makeDocumentInputValue(session, (DBSDocumentLocator) dataContainer, resultsInfo, row, null);
} else {
Object inputCellValue = row.getData()[keyAttribute.getOrdinalPosition()];
@@ -25,13 +25,15 @@ import org.jkiss.dbeaver.model.DBUtils;
import org.jkiss.dbeaver.model.data.*;
import org.jkiss.dbeaver.model.exec.*;
import org.jkiss.dbeaver.model.impl.data.DBDValueError;
import org.jkiss.dbeaver.model.meta.MetaData;
import org.jkiss.dbeaver.model.sql.DBQuotaException;
import org.jkiss.dbeaver.model.struct.DBSDataContainer;
import org.jkiss.dbeaver.model.struct.DBSEntity;
import org.jkiss.utils.CommonUtils;
import java.util.ArrayList;
import java.util.List;
import java.lang.reflect.Method;
import java.util.*;
import java.util.stream.Collectors;
class WebSQLQueryDataReceiver implements DBDDataReceiver {
private static final Log log = Log.getLog(WebSQLQueryDataReceiver.class);
@@ -42,7 +44,7 @@ class WebSQLQueryDataReceiver implements DBDDataReceiver {
private final WebSQLQueryResultSet webResultSet = new WebSQLQueryResultSet();
private DBDAttributeBinding[] bindings;
private List<Object[]> rows = new ArrayList<>();
private List<WebSQLQueryResultSetRow> rows = new ArrayList<>();
private final Number rowLimit;
WebSQLQueryDataReceiver(WebSQLContextInfo contextInfo, DBSDataContainer dataContainer, WebDataFormat dataFormat) {
@@ -70,6 +72,7 @@ class WebSQLQueryDataReceiver implements DBDDataReceiver {
@Override
public void fetchRow(DBCSession session, DBCResultSet resultSet) throws DBCException {
Map<String, Object> metaDataMap = null;
Object[] row = new Object[bindings.length];
for (int i = 0; i < bindings.length; i++) {
@@ -81,12 +84,25 @@ class WebSQLQueryDataReceiver implements DBDDataReceiver {
binding.getMetaAttribute(),
i);
row[i] = cellValue;
if (cellValue != null) {
Method[] methods = cellValue.getClass().getMethods();
for (Method method : methods) {
if (method.isAnnotationPresent(MetaData.class)) {
if (metaDataMap == null) {
metaDataMap = new HashMap<>();
}
Object value = method.invoke(cellValue);
metaDataMap.put(method.getAnnotation(MetaData.class).name(), value);
}
}
}
} catch (Throwable e) {
row[i] = new DBDValueError(e);
}
}
rows.add(row);
rows.add(new WebSQLQueryResultSetRow(row, metaDataMap));
if (rowLimit != null && rows.size() > rowLimit.longValue()) {
throw new DBQuotaException(
@@ -101,7 +117,7 @@ class WebSQLQueryDataReceiver implements DBDDataReceiver {
DBSEntity entity = dataContainer instanceof DBSEntity ? (DBSEntity) dataContainer : null;
try {
DBExecUtils.bindAttributes(session, entity, resultSet, bindings, rows);
DBExecUtils.bindAttributes(session, entity, resultSet, bindings, rows.stream().map(WebSQLQueryResultSetRow::getData).collect(Collectors.toList()));
} catch (DBException e) {
log.error("Error binding attributes", e);
}
@@ -122,15 +138,17 @@ class WebSQLQueryDataReceiver implements DBDDataReceiver {
}
// Convert row values
for (Object[] row : rows) {
for (WebSQLQueryResultSetRow row : rows) {
for (int i = 0; i < bindings.length; i++) {
DBDAttributeBinding binding = bindings[i];
row[i] = WebSQLUtils.makeWebCellValue(webSession, binding, row[i], dataFormat);
row.getData()[i] = WebSQLUtils.makeWebCellValue(webSession, binding, row.getData()[i], dataFormat);
}
}
webResultSet.setColumns(bindings);
webResultSet.setRows(rows.toArray(new Object[0][]));
webResultSet.setRows(List.of(rows.toArray(new WebSQLQueryResultSetRow[0])));
webResultSet.setHasChildrenCollection(resultSet instanceof DBDSubCollectionResultSet);
webResultSet.setSupportsDataFilter(dataContainer.isFeatureSupported(DBSDataContainer.FEATURE_DATA_FILTER));
WebSQLResultsInfo resultsInfo = contextInfo.saveResult(dataContainer, bindings);
webResultSet.setResultsInfo(resultsInfo);
@@ -157,14 +175,14 @@ class WebSQLQueryDataReceiver implements DBDDataReceiver {
// Convert original rows into new rows with leaf attributes
// Extract values for leaf attributes from original row
DBDAttributeBinding[] leafAttributes = leafBindings.toArray(new DBDAttributeBinding[0]);
List<Object[]> newRows = new ArrayList<>();
for (Object[] row : rows) {
List<WebSQLQueryResultSetRow> newRows = new ArrayList<>();
for (WebSQLQueryResultSetRow row : rows) {
Object[] newRow = new Object[leafBindings.size()];
for (int i = 0; i < leafBindings.size(); i++) {
DBDAttributeBinding leafAttr = leafBindings.get(i);
try {
//Object topValue = row[leafAttr.getTopParent().getOrdinalPosition()];
Object cellValue = DBUtils.getAttributeValue(leafAttr, leafAttributes, row);
Object cellValue = DBUtils.getAttributeValue(leafAttr, leafAttributes, row.getData());
/*
Object cellValue = leafAttr.getValueHandler().getValueFromObject(
session,
@@ -178,7 +196,7 @@ class WebSQLQueryDataReceiver implements DBDDataReceiver {
newRow[i] = new DBDValueError(e);
}
}
newRows.add(newRow);
newRows.add(new WebSQLQueryResultSetRow(newRow, row.getMetaData()));
}
this.bindings = leafAttributes;
this.rows = newRows;
@@ -19,6 +19,11 @@ package io.cloudbeaver.service.sql;
import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.model.data.DBDAttributeBinding;
import org.jkiss.dbeaver.model.meta.Property;
import org.jkiss.utils.Pair;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
/**
* Web SQL query resultset.
@@ -28,12 +33,15 @@ public class WebSQLQueryResultSet {
private static final Log log = Log.getLog(WebSQLQueryResultSet.class);
private WebSQLQueryResultColumn[] columns;
private Object[][] rows;
private List<WebSQLQueryResultSetRow> rows;
private boolean hasMoreData;
private WebSQLResultsInfo resultsInfo;
private boolean singleEntity = true;
private boolean hasRowIdentifier;
private boolean hasChildrenCollection;
private boolean isSupportsDataFilter;
public WebSQLQueryResultSet() {
}
@@ -60,11 +68,17 @@ public class WebSQLQueryResultSet {
}
@Property
@Deprecated
public Object[][] getRows() {
return rows.stream().map(WebSQLQueryResultSetRow::getData).toArray(x -> new Object[x][1]);
}
@Property
public List<WebSQLQueryResultSetRow> getRowsWithMetaData() {
return rows;
}
public void setRows(Object[][] rows) {
public void setRows(List<WebSQLQueryResultSetRow> rows) {
this.rows = rows;
}
@@ -102,4 +116,22 @@ public class WebSQLQueryResultSet {
public void setHasRowIdentifier(boolean hasRowIdentifier) {
this.hasRowIdentifier = hasRowIdentifier;
}
@Property
public boolean isHasChildrenCollection() {
return hasChildrenCollection;
}
public void setHasChildrenCollection(boolean hasSuCollection) {
this.hasChildrenCollection = hasSuCollection;
}
@Property
public boolean isSupportsDataFilter() {
return isSupportsDataFilter;
}
public void setSupportsDataFilter(boolean supportsDataFilter) {
isSupportsDataFilter = supportsDataFilter;
}
}
@@ -0,0 +1,31 @@
package io.cloudbeaver.service.sql;
import java.util.Map;
public class WebSQLQueryResultSetRow {
private Object[] data;
private Map<String, Object> metaData;
public WebSQLQueryResultSetRow(Object[] data, Map<String, Object> metaData) {
this.data = data;
this.metaData = metaData;
}
public Object[] getData() {
return data;
}
public Map<String, Object> getMetaData() {
return metaData;
}
public void setData(Object[] data) {
this.data = data;
}
public void setMetaData(Map<String, Object> metaData) {
this.metaData = metaData;
}
}
@@ -25,9 +25,11 @@ import org.jkiss.dbeaver.model.DBPDataKind;
import org.jkiss.dbeaver.model.data.DBDDocument;
import org.jkiss.dbeaver.model.exec.DBCException;
import org.jkiss.dbeaver.model.meta.Property;
import org.jkiss.utils.Pair;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* Web SQL query results.
@@ -86,16 +88,16 @@ public class WebSQLQueryResults {
}
List<WebSQLDatabaseDocument> documents = new ArrayList<>();
for (Object[] row : resultSet.getRows()) {
if (row.length != 1) {
for (WebSQLQueryResultSetRow row : resultSet.getRowsWithMetaData()) {
if (row.getData().length != 1) {
log.debug("Non-document row content");
}
if (row[0] == null) {
if (row.getData()[0] == null) {
documents.add(null);
} else if (row[0] instanceof DBDDocument) {
documents.add(new WebSQLDatabaseDocument(webSession, (DBDDocument) row[0]));
} else if (row.getData()[0] instanceof DBDDocument) {
documents.add(new WebSQLDatabaseDocument(webSession, (DBDDocument) row.getData()[0]));
} else {
log.debug("Non-document row value: " + row[0].getClass().getName());
log.debug("Non-document row value: " + row.getData()[0].getClass().getName());
}
}
@@ -16,6 +16,7 @@
*/
package io.cloudbeaver.service.sql;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.model.data.json.JSONUtils;
import java.util.Map;
@@ -28,12 +29,16 @@ public class WebSQLResultsRow {
private Object[] data;
private Map<String, Object> updateValues;
@Nullable
private Map<String, Object> metaData;
public WebSQLResultsRow() {
}
public WebSQLResultsRow(Map<String, Object> map) {
data = JSONUtils.getObjectList(map, "data").toArray();
updateValues = JSONUtils.getObject(map, "updateValues");
metaData = JSONUtils.getObject(map, "metaData");
}
public Object[] getData() {
@@ -43,4 +48,9 @@ public class WebSQLResultsRow {
public Map<String, Object> getUpdateValues() {
return updateValues;
}
@Nullable
public Map<String, Object> getMetaData() {
return metaData;
}
}
@@ -33,9 +33,7 @@ import org.jkiss.dbeaver.model.DBPDataSource;
import org.jkiss.dbeaver.model.DBPDataSourceContainer;
import org.jkiss.dbeaver.model.DBUtils;
import org.jkiss.dbeaver.model.data.DBDAttributeBinding;
import org.jkiss.dbeaver.model.exec.DBCException;
import org.jkiss.dbeaver.model.exec.DBCLogicalOperator;
import org.jkiss.dbeaver.model.exec.DBExecUtils;
import org.jkiss.dbeaver.model.exec.*;
import org.jkiss.dbeaver.model.impl.sql.BasicSQLDialect;
import org.jkiss.dbeaver.model.navigator.DBNNode;
import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor;
@@ -50,6 +48,7 @@ import org.jkiss.dbeaver.model.sql.parser.SQLScriptParser;
import org.jkiss.dbeaver.model.sql.registry.SQLGeneratorConfigurationRegistry;
import org.jkiss.dbeaver.model.sql.registry.SQLGeneratorDescriptor;
import org.jkiss.dbeaver.model.struct.DBSDataContainer;
import org.jkiss.dbeaver.model.struct.DBSEntity;
import org.jkiss.dbeaver.model.struct.DBSObject;
import org.jkiss.dbeaver.model.struct.DBSWrapper;
import org.jkiss.dbeaver.utils.RuntimeUtils;
@@ -60,9 +59,6 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.Collectors;
/**
@@ -1,46 +1,47 @@
mutation getSqlExecuteTaskResults(
$taskId: ID!
) {
result: asyncSqlExecuteResults(
taskId: $taskId
) {
duration
statusMessage
filterText
fullQuery
results {
title
updateRowCount
sourceQuery
dataFormat
resultSet {
id
columns {
dataKind
entityName
fullTypeName
icon
label
maxLength
name
position
precision
required
readOnly
readOnlyStatus
scale
typeName
supportedOperations {
id
expression
argumentCount
}
}
rows
singleEntity
hasMoreData
hasRowIdentifier
}
mutation getSqlExecuteTaskResults($taskId: ID!) {
result: asyncSqlExecuteResults(taskId: $taskId) {
duration
statusMessage
filterText
fullQuery
results {
title
updateRowCount
sourceQuery
dataFormat
resultSet {
id
columns {
dataKind
entityName
fullTypeName
icon
label
maxLength
name
position
precision
required
readOnly
readOnlyStatus
scale
typeName
supportedOperations {
id
expression
argumentCount
}
}
rowsWithMetaData {
data
metaData
}
singleEntity
hasMoreData
hasRowIdentifier
isSupportsDataFilter
hasChildrenCollection
}
}
}
}
@@ -1,33 +1,38 @@
mutation updateResultsDataBatch(
$projectId: ID!
$connectionId: ID!
$contextId: ID!
$resultsId: ID!
$updatedRows: [ SQLResultRow! ]
$deletedRows: [ SQLResultRow! ]
$addedRows: [ SQLResultRow! ]
$projectId: ID!
$connectionId: ID!
$contextId: ID!
$resultsId: ID!
$updatedRows: [SQLResultRow!]
$deletedRows: [SQLResultRow!]
$addedRows: [SQLResultRow!]
) {
result: updateResultsDataBatch(
projectId: $projectId
connectionId: $connectionId
contextId: $contextId
resultsId: $resultsId
result: updateResultsDataBatch(
projectId: $projectId
connectionId: $connectionId
contextId: $contextId
resultsId: $resultsId
updatedRows: $updatedRows
deletedRows: $deletedRows
addedRows: $addedRows
) {
duration
filterText
results {
updateRowCount
resultSet {
id
rows
singleEntity
hasMoreData
hasRowIdentifier
}
updatedRows: $updatedRows
deletedRows: $deletedRows
addedRows: $addedRows
) {
duration
filterText
results {
updateRowCount
resultSet {
id
rowsWithMetaData {
data
metaData
}
singleEntity
hasMoreData
hasRowIdentifier
isSupportsDataFilter
hasChildrenCollection
}
}
}
}
}
@@ -255,8 +255,8 @@ export class ContainerDataSource extends ResultSetDataSource<IDataContainerOptio
contextId: executionContextInfo.id,
dataFormat: result.dataFormat!,
updateRowCount: result.updateRowCount || 0,
loadedFully: (result.resultSet?.rows?.length || 0) < limit,
count: result.resultSet?.rows?.length || 0,
loadedFully: (result.resultSet?.rowsWithMetaData?.length || 0) < limit,
count: result.resultSet?.rowsWithMetaData?.length || 0,
totalCount: null,
data: result.resultSet,
}));
@@ -36,8 +36,8 @@ export class DataViewerTableService {
return this.tableViewerStorageService.has(tableId);
}
get(modelId: string): IDatabaseDataModel<any, any> | undefined {
return this.tableViewerStorageService.get(modelId);
get(tableId: string): IDatabaseDataModel<any, any> | undefined {
return this.tableViewerStorageService.get(tableId);
}
async removeTableModel(tableId: string): Promise<void> {
@@ -21,11 +21,11 @@ export class DocumentDataAction extends DatabaseDataResultAction<IDocumentElemen
static dataFormat = [ResultDataFormat.Document];
get documents(): IDatabaseDataDocument[] {
return this.result.data?.rows?.map(row => row[0]) || [];
return this.result.data?.rowsWithMetaData?.map(row => row.data[0]) || [];
}
get count(): number {
return this.result.data?.rows?.length || 0;
return this.result.data?.rowsWithMetaData?.length || 0;
}
constructor(source: IDatabaseDataSource<any, IDatabaseResultSet>) {
@@ -37,6 +37,11 @@ export class DocumentDataAction extends DatabaseDataResultAction<IDocumentElemen
});
}
getMetadataForDocument(documentId: string) {
const row = this.result.data?.rowsWithMetaData?.find(row => row.data[0]?.id === documentId);
return row?.metaData;
}
getIdentifier(key: IDocumentElementKey): string {
return key.index.toString();
}
@@ -54,8 +59,12 @@ export class DocumentDataAction extends DatabaseDataResultAction<IDocumentElemen
}
set(index: number, value: IDatabaseDataDocument): void {
if (this.result.data?.rows) {
this.result.data.rows[index][0] = value;
if (this.result.data?.rowsWithMetaData) {
const row = this.result.data.rowsWithMetaData[index];
if (row.data) {
row.data[0] = value;
}
}
}
}
@@ -125,7 +125,8 @@ export class DocumentEditAction extends DatabaseEditAction<IDocumentElementKey,
let rowIndex = 0;
for (const [id, document] of this.editedElements) {
const value = result.data?.rows?.[rowIndex];
const row = result.data?.rowsWithMetaData?.[rowIndex];
const value = row?.data;
if (value !== undefined) {
this.data.set(id, value[0]);
@@ -138,7 +139,8 @@ export class DocumentEditAction extends DatabaseEditAction<IDocumentElementKey,
let rowIndex = 0;
for (const [id, document] of this.editedElements) {
const value = result.data?.rows?.[rowIndex];
const row = result.data?.rowsWithMetaData?.[rowIndex];
const value = row?.data;
if (value !== undefined) {
this.data.set(id, value[0]);
@@ -179,6 +181,7 @@ export class DocumentEditAction extends DatabaseEditAction<IDocumentElementKey,
updatedRows.push({
data: [this.data.get(id)],
metaData: this.data.getMetadataForDocument(document.id),
updateValues: {
// TODO: remove, place new document in data field
0: document,
@@ -0,0 +1,16 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2024 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import type { IDatabaseDataAction } from '../IDatabaseDataAction';
import type { IDatabaseDataResult } from '../IDatabaseDataResult';
export interface IDatabaseDataCacheAction<TKey, TResult extends IDatabaseDataResult> extends IDatabaseDataAction<any, TResult> {
has(key: TKey, scope: symbol): boolean;
get<T>(key: TKey, scope: symbol): T | undefined;
set<T>(key: TKey, scope: symbol, value: T): void;
delete(key: TKey, scope: symbol): void;
}
@@ -0,0 +1,150 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2024 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { makeObservable, observable } from 'mobx';
import { ResultDataFormat } from '@cloudbeaver/core-sdk';
import { DatabaseDataAction } from '../../DatabaseDataAction';
import type { IDatabaseDataSource } from '../../IDatabaseDataSource';
import type { IDatabaseResultSet } from '../../IDatabaseResultSet';
import { databaseDataAction } from '../DatabaseDataActionDecorator';
import type { IDatabaseDataCacheAction } from '../IDatabaseDataCacheAction';
import type { IResultSetElementKey, IResultSetRowKey } from './IResultSetDataKey';
import { ResultSetDataAction } from './ResultSetDataAction';
@databaseDataAction()
export class ResultSetCacheAction
extends DatabaseDataAction<any, IDatabaseResultSet>
implements IDatabaseDataCacheAction<IResultSetElementKey, IDatabaseResultSet>
{
static dataFormat = [ResultDataFormat.Resultset];
private readonly cache: Map<string, Map<symbol, any>>;
constructor(source: IDatabaseDataSource<any, IDatabaseResultSet>, private readonly data: ResultSetDataAction) {
super(source);
this.cache = new Map();
makeObservable<this, 'cache'>(this, {
cache: observable,
});
}
get<T>(key: IResultSetElementKey, scope: symbol): T | undefined {
const keyCache = this.getKeyCache(key);
if (!keyCache) {
return;
}
return keyCache.get(scope);
}
getRow<T>(key: IResultSetRowKey, scope: symbol): T | undefined {
const keyCache = this.getRowCache(key);
if (!keyCache) {
return;
}
return keyCache.get(scope);
}
has(key: IResultSetElementKey, scope: symbol) {
const keyCache = this.getKeyCache(key);
if (!keyCache) {
return false;
}
return keyCache.has(scope);
}
hasRow(key: IResultSetRowKey, scope: symbol) {
const keyCache = this.getRowCache(key);
if (!keyCache) {
return false;
}
return keyCache.has(scope);
}
set<T>(key: IResultSetElementKey, scope: symbol, value: T) {
const keyCache = this.getOrCreateKeyCache(key);
keyCache.set(scope, value);
}
setRow<T>(key: IResultSetRowKey, scope: symbol, value: T) {
const keyCache = this.getOrCreateRowKeyCache(key);
keyCache.set(scope, value);
}
delete(key: IResultSetElementKey, scope: symbol) {
const keyCache = this.getKeyCache(key);
if (keyCache) {
keyCache.delete(scope);
}
}
deleteRow(key: IResultSetRowKey, scope: symbol) {
const keyCache = this.getRowCache(key);
if (keyCache) {
keyCache.delete(scope);
}
}
afterResultUpdate() {
this.cache.clear();
}
dispose(): void {
this.cache.clear();
}
private serializeRowKey(key: IResultSetRowKey) {
return 'row:' + this.data.serializeRowKey(key);
}
private serializeKey(key: IResultSetElementKey) {
return this.data.serialize(key);
}
private getKeyCache(key: IResultSetElementKey) {
return this.cache.get(this.serializeKey(key));
}
private getRowCache(key: IResultSetRowKey) {
return this.cache.get(this.serializeRowKey(key));
}
private getOrCreateKeyCache(key: IResultSetElementKey) {
let keyCache = this.getKeyCache(key);
if (!keyCache) {
keyCache = observable(new Map());
this.cache.set(this.serializeKey(key), keyCache);
}
return keyCache;
}
private getOrCreateRowKeyCache(key: IResultSetRowKey) {
let keyCache = this.getRowCache(key);
if (!keyCache) {
keyCache = observable(new Map());
this.cache.set(this.serializeRowKey(key), keyCache);
}
return keyCache;
}
}
@@ -23,8 +23,8 @@ import type { IResultSetValue } from './ResultSetFormatAction';
export class ResultSetDataAction extends DatabaseDataResultAction<IResultSetElementKey, IDatabaseResultSet> {
static dataFormat = [ResultDataFormat.Resultset];
get rows(): IResultSetValue[][] {
return this.result.data?.rows || [];
get rows() {
return this.result.data?.rowsWithMetaData || [];
}
get columns(): SqlResultColumn[] {
@@ -47,6 +47,10 @@ export class ResultSetDataAction extends DatabaseDataResultAction<IResultSetElem
return ResultSetDataKeysUtils.serializeElementKey(key);
}
serializeRowKey(key: IResultSetRowKey): string {
return ResultSetDataKeysUtils.serialize(key);
}
getDefaultKey(): IResultSetElementKey {
return {
row: {
@@ -60,9 +64,9 @@ export class ResultSetDataAction extends DatabaseDataResultAction<IResultSetElem
}
insertRow(row: IResultSetRowKey, value: IResultSetValue[], shift = 0): IResultSetRowKey | undefined {
if (this.result.data?.rows) {
if (this.result.data?.rowsWithMetaData) {
const index = row.index + shift;
this.result.data.rows.splice(index, 0, value);
this.result.data.rowsWithMetaData.splice(index, 0, { data: value, metaData: {} });
return { index, subIndex: 0 };
}
@@ -71,9 +75,9 @@ export class ResultSetDataAction extends DatabaseDataResultAction<IResultSetElem
}
removeRow(row: IResultSetRowKey, shift = 0): IResultSetRowKey | undefined {
if (this.result.data?.rows) {
if (this.result.data?.rowsWithMetaData) {
const index = row.index + shift;
this.result.data.rows.splice(index, 1);
this.result.data.rowsWithMetaData.splice(index, 1);
return { index: index - 1, subIndex: 0 };
}
@@ -81,8 +85,8 @@ export class ResultSetDataAction extends DatabaseDataResultAction<IResultSetElem
}
setRowValue(row: IResultSetRowKey, value: IResultSetValue[], shift = 0): void {
if (this.result.data?.rows) {
this.result.data.rows[row.index + shift] = value;
if (this.result.data?.rowsWithMetaData) {
this.result.data.rowsWithMetaData[row.index + shift] = { data: value, metaData: this.getRowMetadata(row) };
}
}
@@ -91,7 +95,15 @@ export class ResultSetDataAction extends DatabaseDataResultAction<IResultSetElem
return undefined;
}
return this.rows[row.index];
return this.rows[row.index].data;
}
getRowMetadata(row: IResultSetRowKey) {
if (row.index >= this.rows.length) {
return undefined;
}
return this.rows[row.index].metaData;
}
getCellValue(cell: IResultSetElementKey): IResultSetValue | undefined {
@@ -99,7 +111,7 @@ export class ResultSetDataAction extends DatabaseDataResultAction<IResultSetElem
return undefined;
}
return this.rows[cell.row.index][cell.column.index];
return this.rows[cell.row.index].data?.[cell.column.index];
}
getContent(cell: IResultSetElementKey): IResultSetContentValue | null {
@@ -7,14 +7,16 @@
*/
import type { IResultSetColumnKey, IResultSetElementKey, IResultSetRowKey } from './IResultSetDataKey';
export type SerializableKey = IResultSetColumnKey | IResultSetRowKey;
export const ResultSetDataKeysUtils = {
serializeElementKey(elementKey: IResultSetElementKey): string {
return this.serialize(elementKey.column) + this.serialize(elementKey.row);
return this.serialize(elementKey.column) + '.' + this.serialize(elementKey.row);
},
isElementsKeyEqual(a: IResultSetElementKey, b: IResultSetElementKey) {
return this.isEqual(a.column, b.column) && this.isEqual(a.row, b.row);
},
serialize(key: IResultSetColumnKey | IResultSetRowKey): string {
serialize(key: SerializableKey): string {
let base = `${key.index}`;
if ('subIndex' in key) {
@@ -23,7 +25,7 @@ export const ResultSetDataKeysUtils = {
return base;
},
isEqual<T extends IResultSetColumnKey | IResultSetRowKey>(a: T, b: T): boolean {
isEqual<T extends SerializableKey>(a: T, b: T): boolean {
if (a.index !== b.index) {
return false;
}
@@ -316,7 +316,7 @@ export class ResultSetEditAction extends DatabaseEditAction<IResultSetElementKey
}
applyPartialUpdate(result: IDatabaseResultSet): void {
if (result.data?.rows?.length !== this.updates.length) {
if (result.data?.rowsWithMetaData?.length !== this.updates.length) {
console.warn('ResultSetEditAction: returned data differs from performed update');
}
@@ -340,7 +340,7 @@ export class ResultSetEditAction extends DatabaseEditAction<IResultSetElementKey
}, 0);
for (const update of tempUpdates) {
const value = result.data?.rows?.[update.rowIndex];
const value = result.data?.rowsWithMetaData?.[update.rowIndex]?.data;
const row = update.update.row;
const type = update.update.type;
@@ -498,6 +498,7 @@ export class ResultSetEditAction extends DatabaseEditAction<IResultSetElementKey
}
return obj;
}, {}),
metaData: this.data.getRowMetadata(update.row),
});
}
break;
@@ -519,7 +520,7 @@ export class ResultSetEditAction extends DatabaseEditAction<IResultSetElementKey
}
const deletedRows = batch.deletedRows as SqlResultRow[];
deletedRows.push({ data: replaceBlobsWithNull(update.update) });
deletedRows.push({ data: replaceBlobsWithNull(update.update), metaData: this.data.getRowMetadata(update.row) });
break;
}
}
@@ -36,7 +36,7 @@ export class ResultSetViewAction extends DatabaseDataAction<any, IDatabaseResult
}
get rows(): IResultSetValue[][] {
return this.data.rows;
return this.data.rows.map(row => row.data || []);
}
get columns(): SqlResultColumn[] {
@@ -43,6 +43,7 @@ export * from './DatabaseDataModel/Actions/IDatabaseDataFormatAction';
export * from './DatabaseDataModel/Actions/IDatabaseDataMetadataAction';
export * from './DatabaseDataModel/Actions/IDatabaseDataResultAction';
export * from './DatabaseDataModel/Actions/IDatabaseDataSelectAction';
export * from './DatabaseDataModel/Actions/ResultSet/ResultSetCacheAction';
export * from './DatabaseDataModel/DatabaseDataAction';
export * from './DatabaseDataModel/DatabaseDataActions';
export * from './DatabaseDataModel/DatabaseDataFormat';
@@ -47,6 +47,7 @@ export default [
['data_viewer_refresh_result_set', 'Refresh result set'],
['data_viewer_total_count_tooltip', 'Get total count'],
['data_viewer_total_count_failed', 'Failed to get total count'],
['data_viewer_model_not_loaded', 'Table model is not loaded'],
['settings_data_editor', 'Data Editor'],
['settings_data_editor_disable_edit_name', 'Disable Edit'],
['settings_data_editor_disable_edit_description', 'Disable editing of data in Data Viewer'],
@@ -40,6 +40,7 @@ export default [
['data_viewer_refresh_result_set', 'Refresh result set'],
['data_viewer_total_count_tooltip', 'Get total count'],
['data_viewer_total_count_failed', 'Failed to get total count'],
['data_viewer_model_not_loaded', 'Table model is not loaded'],
['settings_data_editor', 'Data Editor'],
['settings_data_editor_disable_edit_name', 'Disable Edit'],
['settings_data_editor_disable_edit_description', 'Disable editing of data in Data Viewer'],
@@ -41,6 +41,7 @@ export default [
['data_viewer_refresh_result_set', 'Обновить резалт сет'],
['data_viewer_total_count_tooltip', 'Получить количество записей'],
['data_viewer_total_count_failed', 'Не удалось получить количество записей'],
['data_viewer_model_not_loaded', 'Не удалось загрузить модель таблицы'],
['settings_data_editor', 'Редактор данных'],
['settings_data_editor_disable_edit_name', 'Отключить редактирование'],
['settings_data_editor_disable_edit_description', 'Отключить редактирование данных'],
@@ -47,6 +47,7 @@ export default [
['data_viewer_refresh_result_set', 'Refresh result set'],
['data_viewer_total_count_failed', 'Failed to get total count'],
['data_viewer_total_count_tooltip', 'Get total count'],
['data_viewer_model_not_loaded', 'Table model is not loaded'],
['settings_data_editor', 'Data Editor'],
['settings_data_editor_disable_edit_name', 'Disable Edit'],
['settings_data_editor_disable_edit_description', 'Disable editing of data in Data Viewer'],
@@ -280,8 +280,8 @@ export class QueryDataSource<TOptions extends IDataQueryOptions = IDataQueryOpti
contextId: executionContextInfo.id,
dataFormat: result.dataFormat!,
updateRowCount: result.updateRowCount || 0,
loadedFully: (result.resultSet?.rows?.length || 0) < limit,
count: result.resultSet?.rows?.length || 0,
loadedFully: (result.resultSet?.rowsWithMetaData?.length || 0) < limit,
count: result.resultSet?.rowsWithMetaData?.length || 0,
totalCount: null,
// allays returns false
// || !result.resultSet?.hasMoreData,