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
65 changes: 65 additions & 0 deletions packages/core/src/bigquery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,15 @@ export type NoRowsError = Err<"NoRows">;

export type Client = Readonly<{
getTable(ref: TableReference): Promise<Result<UnknownError, Table>>;
getTableRows(
ref: TableReference,
options?: { maxResults?: number }
): Promise<
Result<
UnknownError | NoRowsError,
{ structs: Array<StructuralRow>; page: Page; table: Table }
>
>;
createRunJob(
query: Omit<Query, "dryRun" | "query"> & { query: string }
): Promise<
Expand Down Expand Up @@ -183,8 +192,64 @@ export async function createClient(
);
};

const getTableRows = async (
ref: TableReference,
options: { maxResults?: number } = {}
): Promise<
Result<
UnknownError | NoRowsError,
{ structs: Array<StructuralRow>; page: Page; table: Table }
>
> => {
return tryCatch(
async () => {
const tableResult = await getTable(ref);
if (!tableResult.success) {
throw new Error(tableResult.value.reason);
}
const table = tableResult.value;

const [rows] = await bigQuery
.dataset(ref.datasetId)
.table(ref.tableId)
.getRows({
maxResults: options.maxResults || 100,
wrapIntegers: true,
});

if (!rows || rows.length === 0) {
throw new Error("No rows found in the table");
}

const structs = rows as Array<StructuralRow>;

// Create a simple page object since we're not dealing with query pagination
const page = getPage({
totalRows: table.numRows || "0",
rows: structs.length,
});

return { structs, page, table };
},
(err) => {
const errMessage = errorToString(err);
if (errMessage.includes("No rows found")) {
return {
type: "NoRows" as const,
reason: errMessage,
};
}
return {
type: "Unknown" as const,
reason: errMessage,
};
}
);
};

const client: Client = {
getTable,
getTableRows,

async createRunJob(jobOptions) {
const dryRunJobResult = await runQuery({
Expand Down
11 changes: 4 additions & 7 deletions packages/extension/src/previewer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,16 @@ export const createPreviewer = ({
}) => {
return {
async preview(tableReference: TableReference) {
const id = getTableName(tableReference);
const {
previewer: { rowsPerPage },
} = configManager.get();
const query =
`SELECT * FROM \`${id}\`` +
(rowsPerPage ? ` LIMIT ${rowsPerPage}` : "");
logger.log("query:", query);

const runnerResult = await runnerManager.preview({
logger.log("previewing table:", getTableName(tableReference));

const runnerResult = await runnerManager.previewTable({
title: tableReference.tableId,
query,
tableReference,
maxResults: rowsPerPage || 100,
});
if (!runnerResult.success) {
logger.error(runnerResult);
Expand Down
111 changes: 111 additions & 0 deletions packages/extension/src/runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -502,6 +502,117 @@ export function createRunnerManager({
return createResult;
},

async previewTable({
title,
tableReference,
maxResults,
}: Readonly<{
title: string;
tableReference: TableReference;
maxResults?: number;
}>): Promise<
Result<
Err<"Unknown" | "Authentication" | "NoRows">,
{
run(): Promise<void>;
dispose(): void;
}
>
> {
const runnerId: RunnerID = `table://${checksum(
JSON.stringify(tableReference)
)}`;
const logger = parentLogger.createChild(runnerId);

const status = statusManager.get(runnerId);

return succeed({
async run() {
status.loadBilled();

const config = configManager.get();

const clientResult = await createClient({
keyFilename: config.keyFilename,
projectId: tableReference.projectId,
location: config.location,
});
if (!clientResult.success) {
logger.error(clientResult);
status.errorBilled();
const err = unwrap(clientResult);
errorManager.show(err);
return;
}
const client = unwrap(clientResult);

const getTableRowsResult = await client.getTableRows(tableReference, {
maxResults: maxResults || config.previewer.rowsPerPage || 100,
});

if (!getTableRowsResult.success) {
logger.error(getTableRowsResult);
status.errorBilled();
const err = unwrap(getTableRowsResult);
errorManager.show(err);
return;
}

const { structs, page, table } = unwrap(getTableRowsResult);

logger.log(`fetched table data: ${structs.length} rows`);
status.succeedBilled({
bytes: "0 B", // No query cost!
cacheHit: false,
});

const getRendererResult = await rendererManager.create({
runnerId,
title,
});
if (!getRendererResult.success) {
logger.error(getRendererResult);
errorManager.show(getRendererResult.value);
status.errorBilled();
return;
}
const renderer = unwrap(getRendererResult);

const rendererOpenResult = await renderer.startProcessing();
if (!rendererOpenResult.success) {
logger.error(rendererOpenResult);
await renderer.failProcessing(rendererOpenResult.value);
status.errorBilled();
return;
}

renderer.reveal();

// Only render the rows, no schema or job information
const renderRowsResult = await renderer.renderRows({
structs,
table,
page,
});
if (!renderRowsResult.success) {
logger.error(renderRowsResult);
await renderer.failProcessing(renderRowsResult.value);
return;
}

await renderer.successProcessing();
},

dispose() {
const renderer = rendererManager.get({ runnerId });
if (!renderer || renderer.disposed) {
return;
}
runners.delete(runnerId);
},
});
},

get(runnerId: RunnerID) {
return runners.get(runnerId);
},
Expand Down
2 changes: 1 addition & 1 deletion packages/shared/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import type {
} from "@google-cloud/bigquery";
import type { Err } from ".";

export type RunnerID = `${"file" | "query"}://${string}`;
export type RunnerID = `${"file" | "query" | "table"}://${string}`;

export type Field = PrimitiveField | StructField;
export type PrimitiveField = Readonly<{
Expand Down