pFad - Phone/Frame/Anonymizer/Declutterfier! Saves Data!


--- a PPN by Garber Painting Akron. With Image Size Reduction included!

URL: http://github.com/bytebase/bytebase/pull/19539/files

"https://github.githubassets.com/assets/primer-primitives-6da842159062d25e.css" /> feat: add MongoDB document/table result views by h3n4l · Pull Request #19539 · bytebase/bytebase · GitHub
Skip to content
Merged
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
5 changes: 1 addition & 4 deletions frontend/src/composables/useExecuteSQL.ts
Original file line number Diff line number Diff line change
Expand Up @@ -324,10 +324,7 @@ const useExecuteSQL = () => {
});

const instanceResource = getInstanceResource(database);
if (
instanceResource.engine === Engine.MONGODB ||
instanceResource.engine === Engine.COSMOSDB
) {
if (instanceResource.engine === Engine.COSMOSDB) {
flattenNoSQLResult(resultSet);
}

Expand Down
66 changes: 66 additions & 0 deletions frontend/src/composables/utils.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { create as createProto } from "@bufbuild/protobuf";
import { describe, expect, test } from "vitest";
import {
QueryResultSchema,
QueryRowSchema,
RowValueSchema,
} from "@/types/proto-es/v1/sql_service_pb";
import { flattenNoSQLQueryResult } from "./utils";

describe("flattenNoSQLQueryResult", () => {
test("flattens raw MongoDB document rows into table columns on demand", () => {
const result = createProto(QueryResultSchema, {
columnNames: ["result"],
columnTypeNames: ["TEXT"],
rows: [
createProto(QueryRowSchema, {
values: [
createProto(RowValueSchema, {
kind: {
case: "stringValue",
value: JSON.stringify({
_id: { $oid: "507f1f77bcf86cd799439011" },
name: "Ada",
profile: {
age: { $numberInt: "36" },
},
}),
},
}),
],
}),
],
});

const flattened = flattenNoSQLQueryResult(result);

expect(flattened?.columnNames).toEqual(["_id", "name", "profile"]);
expect(flattened?.columnTypeNames).toEqual(["TEXT", "TEXT", "TEXT"]);
expect(flattened?.rows[0]?.values[0]?.kind.value).toBe(
"507f1f77bcf86cd799439011"
);
expect(flattened?.rows[0]?.values[1]?.kind.value).toBe("Ada");
expect(flattened?.rows[0]?.values[2]?.kind.value).toBe('{"age":36}');
});

test("returns undefined for non-document result sets", () => {
const result = createProto(QueryResultSchema, {
columnNames: ["name"],
columnTypeNames: ["TEXT"],
rows: [
createProto(QueryRowSchema, {
values: [
createProto(RowValueSchema, {
kind: {
case: "stringValue",
value: "Ada",
},
}),
],
}),
],
});

expect(flattenNoSQLQueryResult(result)).toBeUndefined();
});
});
113 changes: 70 additions & 43 deletions frontend/src/composables/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -253,55 +253,82 @@ const convertAnyToRowValue = (

export const flattenNoSQLResult = (resultSet: SQLResultSetV1) => {
for (const result of resultSet.results) {
const { columns, columnIndexMap } = getNoSQLColumns(result.rows);

const rows: QueryRow[] = [];
const columnTypeNames: string[] = Array.from({
length: columns.length,
}).map((_) => "TEXT");

for (const row of result.rows) {
if (
row.values.length !== 1 ||
row.values[0].kind.case !== "stringValue"
) {
continue;
}
// Use lossless-json to preserve precision for large integers (> 2^53-1)
const data = losslessParse(
row.values[0].kind.value,
null,
losslessReviver
) as Record<string, unknown>;
const values: RowValue[] = Array.from({ length: columns.length }).map(
(_) =>
createProto(RowValueSchema, {
kind: {
case: "nullValue",
value: NullValue.NULL_VALUE,
},
})
);
const flattened = flattenNoSQLQueryResult(result);
if (!flattened) {
continue;
}

for (const [key, value] of Object.entries(data)) {
const index = columnIndexMap.get(key) ?? 0;
result.rows = flattened.rows;
result.columnNames = flattened.columnNames;
result.columnTypeNames = flattened.columnTypeNames;
}
};

const { value: formatted, type } = convertAnyToRowValue(value, true);
values[index] = formatted;
columnTypeNames[index] = type;
}
export const flattenNoSQLQueryResult = (
result: QueryResult
): QueryResult | undefined => {
if (
result.columnNames.length !== 1 ||
result.columnNames[0] !== "result" ||
result.rows.length === 0
) {
return undefined;
}

rows.push(
createProto(QueryRowSchema, {
values: values,
})
);
const { columns, columnIndexMap } = getNoSQLColumns(result.rows);
if (columns.length === 0) {
return undefined;
}

const rows: QueryRow[] = [];
const columnTypeNames: string[] = Array.from({ length: columns.length }).map(
() => "TEXT"
);

for (const row of result.rows) {
if (row.values.length !== 1 || row.values[0].kind.case !== "stringValue") {
return undefined;
}

result.rows = rows;
result.columnNames = columns;
result.columnTypeNames = columnTypeNames;
// Use lossless-json to preserve precision for large integers (> 2^53-1)
const data = losslessParse(
row.values[0].kind.value,
null,
losslessReviver
) as Record<string, unknown>;
const values: RowValue[] = Array.from({ length: columns.length }).map(() =>
createProto(RowValueSchema, {
kind: {
case: "nullValue",
value: NullValue.NULL_VALUE,
},
})
);

for (const [key, value] of Object.entries(data)) {
const index = columnIndexMap.get(key) ?? 0;
const { value: formatted, type } = convertAnyToRowValue(value, true);
values[index] = formatted;
columnTypeNames[index] = type;
}

rows.push(
createProto(QueryRowSchema, {
values,
})
);
}

return createProto(QueryResultSchema, {
columnNames: columns,
columnTypeNames,
rows,
rowsCount: BigInt(rows.length),
statement: result.statement,
latency: result.latency,
error: result.error,
masked: result.masked,
});
};

// Parses the hits array from an Elasticsearch _search QueryResult's "hits" column.
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/utils/storage-keys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export const STORAGE_KEY_SQL_EDITOR_DETAIL_FORMAT =
"bb.sql-editor.detail-panel.format";
export const STORAGE_KEY_SQL_EDITOR_DETAIL_LINE_WRAP =
"bb.sql-editor.detail-panel.line-wrap";
export const STORAGE_KEY_SQL_EDITOR_ES_TABLE_VIEW =
export const STORAGE_KEY_SQL_EDITOR_NOSQL_TABLE_VIEW =
"bb.sql-editor.es-table-view";
export const STORAGE_KEY_MY_ISSUES_TAB = "bb.components.MY_ISSUES.id";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@
</NTooltip>
</div>
<div class="flex justify-between items-center shrink-0 gap-x-2">
<div v-if="isESSearchResult" class="flex items-center">
<NSwitch v-model:value="esTableView" size="small" />
<div v-if="supportsTableViewToggle" class="flex items-center">
<NSwitch v-model:value="noSQLTableView" size="small" />
<span class="ml-1 whitespace-nowrap text-sm text-gray-500">
{{ $t("sql-editor.table-view") }}
</span>
Expand Down Expand Up @@ -286,7 +286,10 @@ import DataExportButton from "@/components/DataExportButton.vue";
import EllipsisText from "@/components/EllipsisText.vue";
import { CopyButton, Drawer, RichDatabaseName } from "@/components/v2";
import { useExecuteSQL } from "@/composables/useExecuteSQL";
import { flattenElasticsearchSearchResult } from "@/composables/utils";
import {
flattenElasticsearchSearchResult,
flattenNoSQLQueryResult,
} from "@/composables/utils";
import { DISMISS_PLACEHOLDER } from "@/plugins/ai/components/state";
import { useSQLEditorStore, useSQLEditorTabStore } from "@/store";
import type {
Expand All @@ -309,7 +312,7 @@ import {
isNullOrUndefined,
type SearchParams,
} from "@/utils";
import { STORAGE_KEY_SQL_EDITOR_ES_TABLE_VIEW } from "@/utils/storage-keys";
import { STORAGE_KEY_SQL_EDITOR_NOSQL_TABLE_VIEW } from "@/utils/storage-keys";
import {
provideSQLResultViewContext,
type SQLResultViewContext,
Expand Down Expand Up @@ -406,23 +409,43 @@ const viewMode = computed((): ViewMode => {

const engine = computed(() => getInstanceResource(props.database).engine);

const esTableView = useLocalStorage(STORAGE_KEY_SQL_EDITOR_ES_TABLE_VIEW, true);
const noSQLTableView = useLocalStorage(
STORAGE_KEY_SQL_EDITOR_NOSQL_TABLE_VIEW,
true
);

const flattenedESResult = computed(() => {
if (engine.value !== Engine.ELASTICSEARCH) return undefined;
return flattenElasticsearchSearchResult(props.result);
});

const isESSearchResult = computed(() => flattenedESResult.value !== undefined);
const flattenedMongoResult = computed(() => {
if (engine.value !== Engine.MONGODB) return undefined;
return flattenNoSQLQueryResult(props.result);
});

const flattenedNoSQLResult = computed(() => {
if (engine.value === Engine.ELASTICSEARCH) {
return flattenedESResult.value;
}
if (engine.value === Engine.MONGODB) {
return flattenedMongoResult.value;
}
return undefined;
});

const supportsTableViewToggle = computed(
() => flattenedNoSQLResult.value !== undefined
);

const activeResult = computed(() => {
if (isESSearchResult.value && esTableView.value) {
return flattenedESResult.value!;
if (supportsTableViewToggle.value && noSQLTableView.value) {
return flattenedNoSQLResult.value!;
}
return props.result;
});

watch(esTableView, () => {
watch(noSQLTableView, () => {
state.sortState = undefined;
state.searchParams = { query: "", scopes: [] };
state.searchCandidateActiveIndex = -1;
Expand Down Expand Up @@ -661,8 +684,8 @@ const resultRowsText = computed(() => {
});

const isSensitiveColumn = (columnIndex: number): boolean => {
// Column indices don't match when showing flattened ES table view.
if (isESSearchResult.value && esTableView.value) return false;
// Column indices don't match when showing flattened NoSQL table view.
if (supportsTableViewToggle.value && noSQLTableView.value) return false;
const maskingReason = props.result.masked?.[columnIndex];
// Check if maskingReason exists and has actual content (not empty object)
return (
Expand All @@ -674,8 +697,8 @@ const isSensitiveColumn = (columnIndex: number): boolean => {
};

const getMaskingReason = (columnIndex: number) => {
// Column indices don't match when showing flattened ES table view.
if (isESSearchResult.value && esTableView.value) return undefined;
// Column indices don't match when showing flattened NoSQL table view.
if (supportsTableViewToggle.value && noSQLTableView.value) return undefined;
if (!props.result.masked || columnIndex >= props.result.masked.length) {
return undefined;
}
Expand Down
10 changes: 7 additions & 3 deletions frontend/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,13 @@ export default defineConfig({
compiler: "vue3",
}),
yaml(),
CodeInspectorPlugin({
bundler: "vite",
}),
...(process.env.VITEST
? []
: [
CodeInspectorPlugin({
bundler: "vite",
}),
]),
// Export CSP hashes from @vitejs/plugin-legacy for backend to use
exportCspHashes(),
],
Expand Down
Loading
pFad - Phonifier reborn

Pfad - The Proxy pFad © 2024 Your Company Name. All rights reserved.





Check this box to remove all script contents from the fetched content.



Check this box to remove all images from the fetched content.


Check this box to remove all CSS styles from the fetched content.


Check this box to keep images inefficiently compressed and original size.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy