Files
cloudbeaver/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/DatabaseDataSource.ts
T

238 lines
6.3 KiB
TypeScript

/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2021 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 { observable, makeObservable } from 'mobx';
import { ResultDataFormat } from '@cloudbeaver/core-sdk';
import { DatabaseDataActions } from './DatabaseDataActions';
import type { IDatabaseDataAction, IDatabaseDataActionClass } from './IDatabaseDataAction';
import type { IDatabaseDataActions } from './IDatabaseDataActions';
import type { IDatabaseDataEditor, IDatabaseDataResultEditor } from './IDatabaseDataEditor';
import type { IDatabaseDataResult } from './IDatabaseDataResult';
import { DatabaseDataAccessMode, IDatabaseDataSource, IRequestInfo } from './IDatabaseDataSource';
import type { IDatabaseExecutionContext } from './IDatabaseExecutionContext';
export abstract class DatabaseDataSource<TOptions, TResult extends IDatabaseDataResult>
implements IDatabaseDataSource<TOptions, TResult> {
access: DatabaseDataAccessMode;
dataFormat: ResultDataFormat;
supportedDataFormats: ResultDataFormat[];
editor: IDatabaseDataEditor<TResult> | null;
actions: IDatabaseDataActions<TResult>;
results: TResult[];
offset: number;
count: number;
options: TOptions | null;
requestInfo: IRequestInfo;
error: Error | null;
executionContext: IDatabaseExecutionContext | null;
abstract get canCancel(): boolean;
protected disabled: boolean;
private activeRequest: Promise<TResult[]> | null;
private activeSave: Promise<TResult[]> | null;
private lastAction: () => Promise<void>;
constructor() {
makeObservable<DatabaseDataSource<TOptions, TResult>, 'activeRequest' | 'activeSave' | 'disabled'>(this, {
access: observable,
dataFormat: observable,
supportedDataFormats: observable,
editor: observable,
results: observable,
offset: observable,
count: observable,
options: observable,
requestInfo: observable,
error: observable,
executionContext: observable,
disabled: observable,
activeRequest: observable,
activeSave: observable,
});
this.actions = new DatabaseDataActions();
this.access = DatabaseDataAccessMode.Default;
this.results = [];
this.editor = null;
this.offset = 0;
this.count = 0;
this.options = null;
this.disabled = false;
this.activeRequest = null;
this.activeSave = null;
this.executionContext = null;
this.dataFormat = ResultDataFormat.Resultset;
this.supportedDataFormats = [];
this.requestInfo = {
requestDuration: 0,
requestMessage: '',
source: null,
};
this.error = null;
this.lastAction = this.requestData.bind(this);
}
getAction<T extends IDatabaseDataAction<TResult>>(
resultIndex: number,
action: IDatabaseDataActionClass<TResult, T>
): T {
if (!this.hasResult(resultIndex)) {
throw new Error('Result index out of range');
}
return this.actions.get(this.results[resultIndex], action);
}
abstract cancel(): Promise<boolean> | boolean;
hasResult(resultIndex: number): boolean {
return resultIndex < this.results.length;
}
getResult(index: number): TResult | null {
if (this.results.length > index) {
return this.results[index];
}
return null;
}
getEditor(resultIndex: number): IDatabaseDataResultEditor<TResult> {
if (!this.editor) {
throw new Error('Editor was not provided');
}
if (!this.hasResult(resultIndex)) {
throw new Error('Result index out of range');
}
return this.editor.getResultEditor(this.results[resultIndex]);
}
setResults(results: TResult[]): this {
this.editor?.cancelChanges();
this.results = results;
return this;
}
isReadonly(): boolean {
return this.access === DatabaseDataAccessMode.Readonly || this.results.length > 1 || this.disabled;
}
isLoading(): boolean {
return !!this.activeRequest || !!this.activeSave;
}
isDisabled(resultIndex: number): boolean {
return !!this.activeRequest || !!this.activeSave || this.disabled;
}
setEditor(editor: IDatabaseDataEditor<TResult>): this {
this.editor = editor;
return this;
}
setAccess(access: DatabaseDataAccessMode): this {
this.access = access;
return this;
}
setSlice(offset: number, count: number): this {
this.offset = offset;
this.count = count;
return this;
}
setOptions(options: TOptions): this {
this.options = options;
return this;
}
setDataFormat(dataFormat: ResultDataFormat): this {
this.dataFormat = dataFormat;
return this;
}
setSupportedDataFormats(dataFormats: ResultDataFormat[]): this {
this.supportedDataFormats = dataFormats;
this.dataFormat = dataFormats[0]; // set's default format based on supported list, but maybe should be moved to separate method
return this;
}
setExecutionContext(context: IDatabaseExecutionContext | null): this {
this.executionContext = context;
return this;
}
async retry(): Promise<void> {
await this.lastAction();
}
async requestData(): Promise<void> {
if (this.activeSave) {
try {
await this.activeSave;
} finally { }
}
if (this.activeRequest) {
await this.activeRequest;
return;
}
this.lastAction = this.requestData.bind(this);
try {
const promise = this.request(this.results);
if (promise instanceof Promise) {
this.activeRequest = promise;
}
this.editor?.cancelChanges();
this.results = await promise;
} finally {
this.activeRequest = null;
}
}
async saveData(): Promise<void> {
if (this.activeRequest) {
try {
await this.activeRequest;
} finally { }
}
if (this.activeSave) {
await this.activeSave;
return;
}
this.lastAction = this.saveData.bind(this);
try {
const promise = this.save(this.results);
if (promise instanceof Promise) {
this.activeSave = promise;
}
this.results = await promise;
} finally {
this.activeSave = null;
}
}
clearError(): void {
this.error = null;
}
abstract request(prevResults: TResult[]): TResult[] | Promise<TResult[]>;
abstract save(prevResults: TResult[]): Promise<TResult[]> | TResult[];
abstract dispose(): Promise<void>;
}