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
35 changes: 21 additions & 14 deletions precise/src/AsyncTrinoClient.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ class TrinoQueryRunner {
private trinoSchema: string | null = null
private setHeadersCallback: (catalog: string | null, schema: string | null) => void = () => {}

// Authentication: custom headers to include in every Trino request (e.g. Authorization, X-Trino-User)
private requestHeaders: Record<string, string> = {}

SetAllResultsCallback(setAllResults: (n: any[], error: boolean) => any): TrinoQueryRunner {
this.setAllResults = setAllResults
return this
Expand All @@ -47,6 +50,19 @@ class TrinoQueryRunner {
return this
}

// Set custom headers to include in every Trino request (e.g. Authorization, X-Trino-User)
SetRequestHeaders(headers: Record<string, string>): TrinoQueryRunner {
this.requestHeaders = headers
return this
}

// Resolve headers; falls back to X-Trino-User: system if none provided
private resolveHeaders(): Record<string, string> {
return Object.keys(this.requestHeaders).length > 0
? { ...this.requestHeaders }
: { 'X-Trino-User': 'system' }
}

// Add getters for catalog and schema
GetCatalog(): string | null {
return this.trinoCatalog
Expand All @@ -71,9 +87,7 @@ class TrinoQueryRunner {
// cancel query
fetch(nextUri, {
method: 'DELETE',
headers: {
'X-Trino-User': 'system',
},
headers: this.resolveHeaders(),
})
.then((response) => response)
.then((data) => {
Expand Down Expand Up @@ -157,16 +171,11 @@ class TrinoQueryRunner {
const controller = new AbortController()
const timeoutId = setTimeout(() => controller.abort('Timeout: Trino is not responding'), 15000)

// Prepare headers for the request
const headers: Record<string, string> = {
'X-Trino-User': 'system',
}

// Add catalog and schema headers if they exist
// Merge authentication headers with catalog/schema headers
const headers: Record<string, string> = { ...this.resolveHeaders() }
if (this.trinoCatalog) {
headers['X-Trino-Catalog'] = this.trinoCatalog
}

if (this.trinoSchema) {
headers['X-Trino-Schema'] = this.trinoSchema
}
Expand Down Expand Up @@ -256,12 +265,10 @@ class TrinoQueryRunner {
async NextPage(previous: any) {
try {
// fix cors for testing
const nextUri = await previous.nextUri.replace(/^https?:\/\/[^/]+/, '')
const nextUri = previous.nextUri.replace(/^https?:\/\/[^/]+/, '')
const response = await fetch(nextUri, {
method: 'GET',
headers: {
'X-Trino-User': 'system',
},
headers: this.resolveHeaders(),
})

if (!response.ok) {
Expand Down
12 changes: 12 additions & 0 deletions precise/src/QueryCell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ interface QueryCellProps {
height: number
onDrawerToggle: () => void
theme?: string
requestHeaders?: Record<string, string>
}

class QueryCell extends React.Component<QueryCellProps, QueryCellState> {
Expand Down Expand Up @@ -70,6 +71,7 @@ class QueryCell extends React.Component<QueryCellProps, QueryCellState> {
return (
this.props.drawerOpen !== nextProps.drawerOpen ||
this.props.height !== nextProps.height ||
this.props.requestHeaders !== nextProps.requestHeaders ||
this.state.results !== nextState.results ||
this.state.columns !== nextState.columns ||
this.state.response !== nextState.response ||
Expand All @@ -84,6 +86,12 @@ class QueryCell extends React.Component<QueryCellProps, QueryCellState> {
)
}

componentDidUpdate(prevProps: QueryCellProps) {
if (prevProps.requestHeaders !== this.props.requestHeaders && this.props.requestHeaders) {
this.queryRunner.SetRequestHeaders(this.props.requestHeaders)
}
}

handleQueriesChange = () => {
this.setState({ currentQuery: this.props.queries.getCurrentQuery() })
}
Expand Down Expand Up @@ -119,6 +127,10 @@ class QueryCell extends React.Component<QueryCellProps, QueryCellState> {
schema: schema ?? undefined,
})
})

if (this.props.requestHeaders) {
this.queryRunner.SetRequestHeaders(this.props.requestHeaders)
}
}

setRunningQueryId = (queryId: string | null) => {
Expand Down
13 changes: 11 additions & 2 deletions precise/src/QueryEditor.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useRef, useState } from 'react'
import React, { useEffect, useRef, useState } from 'react'
import { styled } from '@mui/material/styles'
import { Box, Drawer, useMediaQuery } from '@mui/material'
import CssBaseline from '@mui/material/CssBaseline'
Expand All @@ -9,11 +9,13 @@ import { darkTheme, lightTheme } from './theme'
import Queries from './schema/Queries'
import QueryInfo from './schema/QueryInfo'
import CatalogViewer from './controls/catalog_viewer/CatalogViewer'
import SchemaProvider from './sql/SchemaProvider'

interface IQueryEditor {
height: number
theme?: 'dark' | 'light'
enableCatalogSearchColumns?: boolean
requestHeaders?: Record<string, string>
}

const DRAWER_WIDTH = 260
Expand Down Expand Up @@ -71,14 +73,19 @@ const AppBar = styled(MuiAppBar, {
],
}))

export const QueryEditor = ({ height, theme, enableCatalogSearchColumns }: IQueryEditor) => {
export const QueryEditor = ({ height, theme, enableCatalogSearchColumns, requestHeaders }: IQueryEditor) => {
const [queries, setQueries] = useState<Queries>(() => new Queries())
const [drawerOpen, setDrawerOpen] = useState<boolean>(true)
const [queryRunning, setQueryRunning] = useState<boolean>(false)
const [currentQuery, setCurrentQuery] = useState<QueryInfo>(queries.getCurrentQuery())
const prefersDarkMode = useMediaQuery('(prefers-color-scheme: dark)')
const containerRef = useRef(null)

// Propagate request headers to SchemaProvider so catalog browsing is authenticated
useEffect(() => {
SchemaProvider.setRequestHeaders(requestHeaders ?? {})
}, [requestHeaders])

const muiThemeToUse = () => {
if (theme === 'dark') {
return darkTheme
Expand Down Expand Up @@ -188,6 +195,7 @@ export const QueryEditor = ({ height, theme, enableCatalogSearchColumns }: IQuer
onAppendQuery={appendQueryContent}
onDrawerToggle={() => setDrawerOpen(false)}
enableSearchColumns={enableCatalogSearchColumns}
requestHeaders={requestHeaders}
/>
</Drawer>

Expand All @@ -198,6 +206,7 @@ export const QueryEditor = ({ height, theme, enableCatalogSearchColumns }: IQuer
height={height}
onDrawerToggle={() => setDrawerOpen(true)}
theme={theme}
requestHeaders={requestHeaders}
/>
</Main>
</Box>
Expand Down
5 changes: 4 additions & 1 deletion precise/src/controls/catalog_viewer/CatalogViewer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ interface CatalogViewerProps {
onAppendQuery?: (query: string, catalog?: string, schema?: string) => void
onDrawerToggle?: () => void
enableSearchColumns?: boolean
requestHeaders?: Record<string, string>
}

const CatalogViewer: React.FC<CatalogViewerProps> = ({
Expand All @@ -39,6 +40,7 @@ const CatalogViewer: React.FC<CatalogViewerProps> = ({
onAppendQuery,
onDrawerToggle,
enableSearchColumns,
requestHeaders,
}) => {
// Basic state
const [catalogs, setCatalogs] = useState<Map<string, Catalog>>(new Map())
Expand Down Expand Up @@ -89,6 +91,7 @@ const CatalogViewer: React.FC<CatalogViewerProps> = ({
}, [debouncedFilterText, searchColumns, catalogs])

const loadCatalogs = useCallback(async () => {
SchemaProvider.setRequestHeaders(requestHeaders ?? {})
setIsLoading(true)
setErrorMessage(undefined)

Expand All @@ -107,7 +110,7 @@ const CatalogViewer: React.FC<CatalogViewerProps> = ({
setErrorMessage(error instanceof Error ? error.message : 'An unknown error occurred')
setIsLoading(false)
}
}, [])
}, [requestHeaders])

const handleToggle = async (path: string) => {
if (!viewerState.current) return
Expand Down
19 changes: 15 additions & 4 deletions precise/src/sql/SchemaProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,17 @@ class SchemaProvider {
// map of fully qualified table name to tables
static tables: Map<string, Table> = new Map<string, Table>()

// Configurable request headers for authentication
private static requestHeaders: Record<string, string> = {}

static setRequestHeaders(headers: Record<string, string>) {
this.requestHeaders = { ...headers }
}

private static createRunner(): TrinoQueryRunner {
return new TrinoQueryRunner().SetRequestHeaders(this.requestHeaders)
}

static getTableNameList(catalogFilter: string | undefined, schemaFilter: string | undefined): string[] {
// get list from catalogs, because tables may not be resolved
const tableNames: string[] = []
Expand Down Expand Up @@ -68,7 +79,7 @@ class SchemaProvider {
errorCallback: ((error: string) => void) | null = null
) {
// refresh catalogs
new TrinoQueryRunner()
this.createRunner()
.SetAllResultsCallback((results: any[], isError: boolean) => {
for (let i = 0; i < results.length; i++) {
const catalog: Catalog = new Catalog(results[i][0], results[i][1])
Expand All @@ -78,7 +89,7 @@ class SchemaProvider {
this.lastSchemaFetchError = undefined

// refresh tables and schemas for this catalog
new TrinoQueryRunner()
this.createRunner()
.SetAllResultsCallback((results: any[], isError: boolean) => {
for (let i = 0; i < results.length; i++) {
const schemaName = results[i][0]
Expand Down Expand Up @@ -121,7 +132,7 @@ class SchemaProvider {
/* callback returns a table type */
static async getTableRefreshCache(tableRef: TableReference, callback: (table: Table) => void) {
// First try to load all tables in the schema at once
const query = new TrinoQueryRunner()
const query = this.createRunner()
query
.SetAllResultsCallback((results: any[]) => {
// Create a temporary map to hold all tables in this schema
Expand Down Expand Up @@ -186,7 +197,7 @@ class SchemaProvider {
}

private static fallbackToDescribe(tableRef: TableReference, callback: (table: Table) => void) {
const fallbackQuery = new TrinoQueryRunner()
const fallbackQuery = this.createRunner()
fallbackQuery
.SetAllResultsCallback((results: any[]) => {
const table = new Table(tableRef.tableName)
Expand Down