diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9baeb35..5f876f0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,11 +22,8 @@ jobs: with: node-version: '22' - # Temporarily disabling this step due to formatting issues. - # Linter and prettier) currently throws numerous warnings. - # Code style checks will be re-enabled in a future PR that includes a codebase reformat. - # - name: Check code style - # run: npm run check:clean + - name: Check code style + run: npm run check:clean - name: Build package run: npm run package:clean diff --git a/README.md b/README.md index c258ec3..9f3fc3f 100644 --- a/README.md +++ b/README.md @@ -138,6 +138,16 @@ The local URL will be be displayed which you can open in your browser. To build parser: `npm run antlr4ng`, as configured in **package.json** +### Linting and code formatting + +To check code quality and formatting: + +```shell + npm run check +``` + +This command runs both eslint and prettier, as defined in **package.json** + ## Philosophy This UI's purpose is to provide an environment where once the cluster is stood up, executing queries and exploring data sets can be done right away. The idended use cases are: diff --git a/precise/.eslintignore b/precise/.eslintignore new file mode 100644 index 0000000..4150114 --- /dev/null +++ b/precise/.eslintignore @@ -0,0 +1 @@ +src/generated/** diff --git a/precise/.eslintrc.cjs b/precise/.eslintrc.cjs index d6c9537..b172919 100644 --- a/precise/.eslintrc.cjs +++ b/precise/.eslintrc.cjs @@ -14,5 +14,10 @@ module.exports = { 'warn', { allowConstantExport: true }, ], + // ❗ Temporarily disabled due to widespread use of `any` an `unused vars` in the codebase. + // Defining proper types will require significant effort. + // We will address this incrementally in future pull requests. + '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/no-unused-vars': 'off', }, } diff --git a/precise/.prettierignore b/precise/.prettierignore new file mode 100644 index 0000000..4150114 --- /dev/null +++ b/precise/.prettierignore @@ -0,0 +1 @@ +src/generated/** diff --git a/precise/.prettierrc b/precise/.prettierrc new file mode 100644 index 0000000..4e534aa --- /dev/null +++ b/precise/.prettierrc @@ -0,0 +1,7 @@ +{ + "trailingComma": "es5", + "tabWidth": 4, + "semi": false, + "singleQuote": true, + "printWidth": 120 +} diff --git a/precise/src/App.tsx b/precise/src/App.tsx index e67deb2..29fecdf 100644 --- a/precise/src/App.tsx +++ b/precise/src/App.tsx @@ -1,17 +1,12 @@ -import { useState, useEffect } from 'react'; -import QueryApp from './QueryApp'; - -const defaultQuery = "-- enter your query here\n\nselect * from tpch.sf1.lineitem limit 2"; +import QueryApp from './QueryApp' // for now this is a defacto Tab, but we will treat this tab more as a page in the future function App() { - - - return ( -
- -
- ); + return ( +
+ +
+ ) } -export default App; \ No newline at end of file +export default App diff --git a/precise/src/AsyncTrinoClient.tsx b/precise/src/AsyncTrinoClient.tsx index 3b6db36..944edfb 100644 --- a/precise/src/AsyncTrinoClient.tsx +++ b/precise/src/AsyncTrinoClient.tsx @@ -1,256 +1,254 @@ // class to execute Trino queries class TrinoQueryRunner { - - private state : any = {}; - private query : string = ""; - private rowsRead : number = 0; - isRunning : boolean = false; - private cancellationToken : string | null = null; - SetResults = (newResults : any[]) => void {}; + private state: any = {} + private query: string = '' + private rowsRead: number = 0 + isRunning: boolean = false + private cancellationToken: string | null = null + SetResults = (newResults: any[]) => void {} // make this return the TrinoQueryRunner object - private setAllResults = (allResults : any[], error: boolean) => void {}; - SetColumns = (newColumns : any[]) => {}; - private setStatus = (newStatus : any) => {}; - SetScanStats = (newScanStats : any) => {}; - private setErrorMessage = (newErrorMessage : string) => {}; - SetCancelling = () => {}; - SetStopped = () => {}; - SetStarted = () => {}; - pages : any[] = []; - columns : any[] = []; - backoff_delay_msec = 0; - previous_progress = 0; - cancellationReason : string = ""; - + private setAllResults = (allResults: any[], error: boolean) => void {} + SetColumns = (newColumns: any[]) => {} + private setStatus = (newStatus: any) => {} + SetScanStats = (newScanStats: any) => {} + private setErrorMessage = (newErrorMessage: string) => {} + SetCancelling = () => {} + SetStopped = () => {} + SetStarted = () => {} + pages: any[] = [] + columns: any[] = [] + backoff_delay_msec = 0 + previous_progress = 0 + cancellationReason: string = '' + // Add properties to store catalog and schema headers - private trinoCatalog : string | null = null; - private trinoSchema : string | null = null; - private setHeadersCallback: (catalog: string | null, schema: string | null) => void = () => {}; + private trinoCatalog: string | null = null + private trinoSchema: string | null = null + private setHeadersCallback: (catalog: string | null, schema: string | null) => void = () => {} - SetAllResultsCallback(setAllResults: ((n: any[], error : boolean) => any)) : TrinoQueryRunner { - this.setAllResults = setAllResults; - return this; + SetAllResultsCallback(setAllResults: (n: any[], error: boolean) => any): TrinoQueryRunner { + this.setAllResults = setAllResults + return this } - SetErrorMessageCallback(setErrorMessage: ((n: string) => any)) : TrinoQueryRunner { - this.setErrorMessage = setErrorMessage; - return this; + SetErrorMessageCallback(setErrorMessage: (n: string) => any): TrinoQueryRunner { + this.setErrorMessage = setErrorMessage + return this } - SetStatusCallback(setStatus: ((n: any) => any)) : TrinoQueryRunner { - this.setStatus = setStatus; - return this; + SetStatusCallback(setStatus: (n: any) => any): TrinoQueryRunner { + this.setStatus = setStatus + return this } - + // Add method to set the headers callback - SetHeadersCallback(callback: ((catalog: string | null, schema: string | null) => void)) : TrinoQueryRunner { - this.setHeadersCallback = callback; - return this; + SetHeadersCallback(callback: (catalog: string | null, schema: string | null) => void): TrinoQueryRunner { + this.setHeadersCallback = callback + return this } - + // Add getters for catalog and schema - GetCatalog() : string | null { - return this.trinoCatalog; + GetCatalog(): string | null { + return this.trinoCatalog } - - GetSchema() : string | null { - return this.trinoSchema; + + GetSchema(): string | null { + return this.trinoSchema } - FirstColumn() : string[] { - return this.pages.map(page => page.map((row: any[]) => row[0]))[0]; + FirstColumn(): string[] { + return this.pages.map((page) => page.map((row: any[]) => row[0]))[0] } - UpdateStatus(state : any) - { + UpdateStatus(state: any) { // If cancelled, handle here because we need state in order to cancel if (this.cancellationToken) { - state.stats.state = "CANCELLING"; - this.state = state; - this.setStatus(state); + state.stats.state = 'CANCELLING' + this.state = state + this.setStatus(state) const nextUri = state.nextUri.replace('http://localhost:8080', '') // cancel query fetch(nextUri, { method: 'DELETE', headers: { - 'X-Trino-User': 'system', + 'X-Trino-User': 'system', }, }) - .then(response => response) - .then(data => { - state.stats.state = "CANCELLED"; - this.state = state; - this.setStatus(state); - this.cancellationToken = null; - this.setErrorMessage(this.cancellationReason ? this.cancellationReason : 'Query was cancelled'); - this.HandleStopped(); - }) - .catch(error => - { - console.error('Error:', error); - this.setErrorMessage(error.toString()); - this.HandleStopped(); - }); - return; + .then((response) => response) + .then((data) => { + state.stats.state = 'CANCELLED' + this.state = state + this.setStatus(state) + this.cancellationToken = null + this.setErrorMessage(this.cancellationReason ? this.cancellationReason : 'Query was cancelled') + this.HandleStopped() + }) + .catch((error) => { + console.error('Error:', error) + this.setErrorMessage(error.toString()) + this.HandleStopped() + }) + return } - this.state = state; - this.setStatus(state); + this.state = state + this.setStatus(state) if (state.error) { - this.setErrorMessage(state.error.message); + this.setErrorMessage(state.error.message) } } ClearState() { - this.pages = []; - this.columns = []; - this.rowsRead = 0; + this.pages = [] + this.columns = [] + this.rowsRead = 0 } HandleStopped() { - this.isRunning = false; - this.SetStopped(); + this.isRunning = false + this.SetStopped() } - HandleSetAllResults (error : boolean) { + HandleSetAllResults(error: boolean) { // combines all pages into one array - const rows: any[] = []; - this.pages.forEach(page => { + const rows: any[] = [] + this.pages.forEach((page) => { page.forEach((row: any) => { - rows.push(row); - }); - }); + rows.push(row) + }) + }) - this.setAllResults(rows, error); + this.setAllResults(rows, error) } - CancelQuery(cancellationReason : string) { + CancelQuery(cancellationReason: string) { if (this.isRunning && !this.cancellationToken) { - this.cancellationToken = "cancelling"; - this.cancellationReason = cancellationReason; + this.cancellationToken = 'cancelling' + this.cancellationReason = cancellationReason } } - StartQuery(statement : string, catalog?: string, schema?: string) : TrinoQueryRunner { + StartQuery(statement: string, catalog?: string, schema?: string): TrinoQueryRunner { // if running cancel before starting another if (this.isRunning) { - this.CancelQuery(""); - return this; + this.CancelQuery('') + return this } // Set the catalog and schema if provided if (catalog) { - this.trinoCatalog = catalog; + this.trinoCatalog = catalog } - + if (schema) { - this.trinoSchema = schema; + this.trinoSchema = schema } - this.backoff_delay_msec = 0; + this.backoff_delay_msec = 0 - this.isRunning = true; - this.SetStarted(); - this.query = statement; - console.log('Starting query: ' + statement); - this.rowsRead = 0; + this.isRunning = true + this.SetStarted() + this.query = statement + console.log('Starting query: ' + statement) + this.rowsRead = 0 this.ClearState() const controller = new AbortController() - const timeoutId = setTimeout(() => controller.abort("Timeout: Trino is not responding"), 15000) + const timeoutId = setTimeout(() => controller.abort('Timeout: Trino is not responding'), 15000) // Prepare headers for the request const headers: Record = { 'X-Trino-User': 'system', - }; - + } + // Add catalog and schema headers if they exist if (this.trinoCatalog) { - headers['X-Trino-Catalog'] = this.trinoCatalog; + headers['X-Trino-Catalog'] = this.trinoCatalog } - + if (this.trinoSchema) { - headers['X-Trino-Schema'] = this.trinoSchema; + headers['X-Trino-Schema'] = this.trinoSchema } - + fetch('/v1/statement', { - method: 'POST', - headers: headers, - body: statement, - signal: controller.signal + method: 'POST', + headers: headers, + body: statement, + signal: controller.signal, }) - .then(response => { - if (!response.ok) { - throw new Error(response.statusText + ' (' + response.status + ')'); - } - - // Extract headers before parsing the JSON response - this.extractHeaders(response.headers); - - return response.json() - }) - .then(data => { - this.HandleResults(data) - this.UpdateStatus(data) - this.NextPage(data) - }) - .catch(error => { - clearTimeout(timeoutId); - - let errorMessage = 'An unexpected error occurred'; - - if (error instanceof DOMException && error.name === 'AbortError') { - errorMessage = 'Query timed out - Trino server took too long to respond'; - } else if (error instanceof TypeError && error.message.includes('Failed to fetch')) { - if (navigator.onLine === false) { - errorMessage = 'You appear to be offline. Please check your internet connection.'; - } else { - errorMessage = 'Failed to connect to Trino server - the server may be down, unreachable, or incorrectly configured'; + .then((response) => { + if (!response.ok) { + throw new Error(response.statusText + ' (' + response.status + ')') } - } else if (error instanceof Error) { - errorMessage = error.message; - } - - console.error('Error starting query:', errorMessage); - this.setErrorMessage(errorMessage); - this.HandleStopped(); - }); - return this; + // Extract headers before parsing the JSON response + this.extractHeaders(response.headers) + + return response.json() + }) + .then((data) => { + this.HandleResults(data) + this.UpdateStatus(data) + this.NextPage(data) + }) + .catch((error) => { + clearTimeout(timeoutId) + + let errorMessage = 'An unexpected error occurred' + + if (error instanceof DOMException && error.name === 'AbortError') { + errorMessage = 'Query timed out - Trino server took too long to respond' + } else if (error instanceof TypeError && error.message.includes('Failed to fetch')) { + if (navigator.onLine === false) { + errorMessage = 'You appear to be offline. Please check your internet connection.' + } else { + errorMessage = + 'Failed to connect to Trino server - the server may be down, unreachable, or incorrectly configured' + } + } else if (error instanceof Error) { + errorMessage = error.message + } + + console.error('Error starting query:', errorMessage) + this.setErrorMessage(errorMessage) + this.HandleStopped() + }) + + return this } - + // Extract and store Trino headers private extractHeaders(headers: Headers) { - const headerEntries: [string, string][] = []; - + const headerEntries: [string, string][] = [] + // Iterate through all headers and log them headers.forEach((value, key) => { - headerEntries.push([key.toLowerCase(), value]); - }); - + headerEntries.push([key.toLowerCase(), value]) + }) + // Create a map of lowercase header names for case-insensitive lookup - const headerMap = new Map(headerEntries); - + const headerMap = new Map(headerEntries) + // Get SET catalog and schema headers if present (case-insensitive) - const setCatalog = headerMap.get('x-trino-set-catalog'); - const setSchema = headerMap.get('x-trino-set-schema'); - - console.log(`Found SET headers - Catalog: ${setCatalog}, Schema: ${setSchema}`); - + const setCatalog = headerMap.get('x-trino-set-catalog') + const setSchema = headerMap.get('x-trino-set-schema') + + console.log(`Found SET headers - Catalog: ${setCatalog}, Schema: ${setSchema}`) + // Update our stored values when SET headers are present if (setCatalog) { - this.trinoCatalog = setCatalog; - console.log(`Updated catalog to: ${this.trinoCatalog}`); + this.trinoCatalog = setCatalog + console.log(`Updated catalog to: ${this.trinoCatalog}`) } - + if (setSchema) { - this.trinoSchema = setSchema; - console.log(`Updated schema to: ${this.trinoSchema}`); + this.trinoSchema = setSchema + console.log(`Updated schema to: ${this.trinoSchema}`) } - + // Call the callback with the extracted headers if ((setCatalog || setSchema) && this.setHeadersCallback) { - this.setHeadersCallback(this.trinoCatalog, this.trinoSchema); + this.setHeadersCallback(this.trinoCatalog, this.trinoSchema) } } @@ -263,88 +261,84 @@ class TrinoQueryRunner { headers: { 'X-Trino-User': 'system', }, - }); - + }) + if (!response.ok) { - throw new Error(response.statusText); + throw new Error(response.statusText) } - + // Extract headers from each page response - this.extractHeaders(response.headers); - - const data = await response.json(); + this.extractHeaders(response.headers) + + const data = await response.json() + + this.HandleResults(data) + this.UpdateStatus(data) - this.HandleResults(data); - this.UpdateStatus(data); - if (data.nextUri) { // We want to cancel just after the status is updated, otherwise if the queue is QUEUED we will not be able to cancel if (this.cancellationToken) { - return; + return } // backoff delay add 20ms up to 1000ms - this.backoff_delay_msec = Math.min(this.backoff_delay_msec + 20, 1000); - setTimeout(() => this.NextPage(data), this.backoff_delay_msec); - } - else - { - this.HandleSetAllResults(data["stats"]["state"] == "FAILED"); - this.HandleStopped(); + this.backoff_delay_msec = Math.min(this.backoff_delay_msec + 20, 1000) + setTimeout(() => this.NextPage(data), this.backoff_delay_msec) + } else { + this.HandleSetAllResults(data['stats']['state'] == 'FAILED') + this.HandleStopped() console.log('Query finished') } } catch (error) { if (error instanceof Error) { // handle errors of time net::ERR_CONNECTION_REFUSED if (error.message === 'Failed to fetch') { - console.error('Error:', error.message + " - Trino is not running or not reachable"); - this.setErrorMessage(error.message); - } - else { - console.error('Error:', error.message); - this.setErrorMessage(error.message); + console.error('Error:', error.message + ' - Trino is not running or not reachable') + this.setErrorMessage(error.message) + } else { + console.error('Error:', error.message) + this.setErrorMessage(error.message) } } else { // Handle cases where the thrown error is not an Error instance - console.error('An unexpected error occurred:', error); - this.setErrorMessage('An unexpected error occurred'); + console.error('An unexpected error occurred:', error) + this.setErrorMessage('An unexpected error occurred') } - this.HandleStopped(); + this.HandleStopped() } } - HandleResults(data : any) : boolean { + HandleResults(data: any): boolean { if (data.columns && this.columns !== data.columns) { - this.columns = data.columns; - this.SetColumns(data.columns); + this.columns = data.columns + this.SetColumns(data.columns) } - const maxRows = 10000; + const maxRows = 10000 if (data.data) { if (data.data.length + this.rowsRead > maxRows) { // modify this page so we hit maxRows rows exactly - const trim = maxRows - this.rowsRead; - this.rowsRead += trim; - const page : any[] = data.data.slice(0, trim); - this.pages.push(page); + const trim = maxRows - this.rowsRead + this.rowsRead += trim + const page: any[] = data.data.slice(0, trim) + this.pages.push(page) // set error indicating rows were trimmed formatted using maxRows with commas - this.setErrorMessage('Results were trimmed to ' + maxRows.toLocaleString() + ' rows'); - this.SetResults(this.pages); + this.setErrorMessage('Results were trimmed to ' + maxRows.toLocaleString() + ' rows') + this.SetResults(this.pages) // cancel - this.CancelQuery('Results were trimmed to ' + maxRows.toLocaleString() + ' rows'); - return false; - } - else { - this.pages.push(data.data); - this.rowsRead += data.data.length; + this.CancelQuery('Results were trimmed to ' + maxRows.toLocaleString() + ' rows') + return false + } else { + this.pages.push(data.data) + this.rowsRead += data.data.length } - this.SetResults(this.pages); + this.SetResults(this.pages) } - return true; + return true } } -export default TrinoQueryRunner; \ No newline at end of file +export default TrinoQueryRunner diff --git a/precise/src/QueryApp.tsx b/precise/src/QueryApp.tsx index 78622ae..5a58314 100644 --- a/precise/src/QueryApp.tsx +++ b/precise/src/QueryApp.tsx @@ -1,96 +1,104 @@ -import React from "react"; -import QueryCell from "./QueryCell"; -import Queries from './schema/Queries'; -import CatalogViewer from './controls/catalog_viewer/CatalogViewer'; -import './style/layout.css'; -import './style/components.css'; +import React from 'react' +import QueryCell from './QueryCell' +import Queries from './schema/Queries' +import CatalogViewer from './controls/catalog_viewer/CatalogViewer' +import './style/layout.css' +import './style/components.css' interface QueryAppProps {} interface QueryAppState { - queries: Queries; + queries: Queries } class QueryApp extends React.Component { constructor(props: QueryAppProps) { - super(props); + super(props) this.state = { - queries: new Queries() + queries: new Queries(), } } setQueryContent = (query: string, catalog?: string, schema?: string) => { - const currentQuery = this.state.queries.getCurrentQuery(); - const updates: any = {}; - + const currentQuery = this.state.queries.getCurrentQuery() + const updates: any = {} + if (query) { - updates.query = query; + updates.query = query } - + if (catalog) { - updates.catalog = catalog; + updates.catalog = catalog } - + if (schema) { - updates.schema = schema; + updates.schema = schema } - - this.state.queries.updateQuery(currentQuery.id, updates); + + this.state.queries.updateQuery(currentQuery.id, updates) } - + appendQueryContent = (query: string, catalog?: string, schema?: string) => { - const currentQuery = this.state.queries.getCurrentQuery(); - const updates: any = {}; - + const currentQuery = this.state.queries.getCurrentQuery() + const updates: any = {} + if (query) { // Append to existing query, adding newlines as needed - const existingQuery = currentQuery.query || ''; - const separator = existingQuery.trim() === '' ? '' : '\n\n'; - updates.query = existingQuery + separator + query; + const existingQuery = currentQuery.query || '' + const separator = existingQuery.trim() === '' ? '' : '\n\n' + updates.query = existingQuery + separator + query } - + if (catalog) { - updates.catalog = catalog; + updates.catalog = catalog } - + if (schema) { - updates.schema = schema; + updates.schema = schema } - - this.state.queries.updateQuery(currentQuery.id, updates); + + this.state.queries.updateQuery(currentQuery.id, updates) } render() { return (
-
-
+
+
-
- - + }} + > + » Catalogs + +
@@ -99,8 +107,8 @@ class QueryApp extends React.Component {
- ); + ) } } -export default QueryApp; \ No newline at end of file +export default QueryApp diff --git a/precise/src/QueryCell.tsx b/precise/src/QueryCell.tsx index 22424b5..ee0506f 100644 --- a/precise/src/QueryCell.tsx +++ b/precise/src/QueryCell.tsx @@ -1,58 +1,50 @@ -import React from 'react'; -import QueryEditor from './QueryEditor'; -import ResultSet from './ResultSet'; -import Queries from './schema/Queries'; -import QueryInfo from './schema/QueryInfo'; -import AsyncTrinoClient from './AsyncTrinoClient'; -import { - Play, - StopCircle, - Link, - Plus, - FileEdit, - MinusSquare, - PlusSquare -} from 'lucide-react'; -import './style/components.css'; -import './style/query-editor.css'; +import React from 'react' +import QueryEditor from './QueryEditor' +import ResultSet from './ResultSet' +import Queries from './schema/Queries' +import QueryInfo from './schema/QueryInfo' +import AsyncTrinoClient from './AsyncTrinoClient' +import { Play, StopCircle, Link, Plus, FileEdit, MinusSquare, PlusSquare } from 'lucide-react' +import './style/components.css' +import './style/query-editor.css' interface QueryCellState { - results: any[]; - columns: any[]; - response: any; - errorMessage: string; - currentQuery: QueryInfo; - runningQuery: QueryInfo | undefined; + results: any[] + columns: any[] + response: any + errorMessage: string + currentQuery: QueryInfo + runningQuery: QueryInfo | undefined } interface QueryCellProps { - queries: Queries; + queries: Queries } class QueryCell extends React.Component { - private queryRunner: AsyncTrinoClient; - private isQueryCollapsed: boolean = false; + private queryRunner: AsyncTrinoClient + private isQueryCollapsed: boolean = false constructor(props: QueryCellProps) { - super(props); + super(props) this.state = { results: [], columns: [], response: {}, errorMessage: '', currentQuery: this.props.queries.getCurrentQuery(), - runningQuery: undefined - }; - this.queryRunner = new AsyncTrinoClient(); - this.setupQueryRunner(); + runningQuery: undefined, + } + this.queryRunner = new AsyncTrinoClient() + this.setupQueryRunner() } componentDidMount() { - this.props.queries.addChangeListener(this.handleQueriesChange); + this.props.queries.addChangeListener(this.handleQueriesChange) } componentWillUnmount() { - this.props.queries.removeChangeListener(this.handleQueriesChange); + this.props.queries.removeChangeListener(this.handleQueriesChange) } shouldComponentUpdate(nextProps: QueryCellProps, nextState: QueryCellState) { @@ -65,45 +57,48 @@ class QueryCell extends React.Component { this.state.runningQuery !== nextState.runningQuery || this.state.currentQuery !== nextState.currentQuery || this.state.currentQuery.title !== nextState.currentQuery.title - ); + ) } handleQueriesChange = () => { - this.setState({ currentQuery: this.props.queries.getCurrentQuery() }); + this.setState({ currentQuery: this.props.queries.getCurrentQuery() }) } setupQueryRunner() { this.queryRunner.SetResults = (newResults: any[]) => { - this.setState({ results: newResults }); - }; + this.setState({ results: newResults }) + } this.queryRunner.SetColumns = (newColumns: any[]) => { - this.setState({ columns: newColumns }); - }; + this.setState({ columns: newColumns }) + } this.queryRunner.SetStatusCallback((setStatus: (newStatus: any) => any) => { - this.setState({ response: setStatus }); - }); + this.setState({ response: setStatus }) + }) this.queryRunner.SetErrorMessageCallback((newErrorMessage: string) => { - this.setState({ errorMessage: newErrorMessage }); - }); + this.setState({ errorMessage: newErrorMessage }) + }) this.queryRunner.SetStopped = () => { - this.SetStoppedState(); - }; + this.SetStoppedState() + } this.queryRunner.SetStarted = () => { - this.QueryStarted(); - }; + this.QueryStarted() + } this.queryRunner.SetHeadersCallback((catalog: string | null, schema: string | null) => { - this.props.queries.updateQuery(this.state.currentQuery.id, { catalog: catalog ?? undefined, schema: schema ?? undefined }); - }); + this.props.queries.updateQuery(this.state.currentQuery.id, { + catalog: catalog ?? undefined, + schema: schema ?? undefined, + }) + }) } setRunningQueryId = (queryId: string | null) => { - this.setState({ runningQuery: this.state.currentQuery }); + this.setState({ runningQuery: this.state.currentQuery }) } handleQueryChange = (newQuery: string) => { @@ -111,47 +106,54 @@ class QueryCell extends React.Component { } handleTitleChange = (title: string) => { - this.props.queries.updateQuery(this.state.currentQuery.id, { title: title }); + this.props.queries.updateQuery(this.state.currentQuery.id, { title: title }) } ClearResults() { - this.setState({ results: [], columns: [], errorMessage: '' }); + this.setState({ results: [], columns: [], errorMessage: '' }) } QueryStarted() { - this.ClearResults(); - this.setRunningQueryId(this.state.currentQuery.id); - this.forceUpdate(); // To ensure the play/stop icon updates + this.ClearResults() + this.setRunningQueryId(this.state.currentQuery.id) + this.forceUpdate() // To ensure the play/stop icon updates } SetStoppedState() { - this.forceUpdate(); // To ensure the play/stop icon updates + this.forceUpdate() // To ensure the play/stop icon updates } Execute() { - this.queryRunner.StartQuery(this.state.currentQuery.query, this.state.currentQuery.catalog, this.state.currentQuery.schema); + this.queryRunner.StartQuery( + this.state.currentQuery.query, + this.state.currentQuery.catalog, + this.state.currentQuery.schema + ) } toggleQueryCollapse = () => { - const queryEditor = document.getElementById('query-editor'); + const queryEditor = document.getElementById('query-editor') if (queryEditor) { - this.isQueryCollapsed = !this.isQueryCollapsed; - queryEditor.style.display = this.isQueryCollapsed ? 'none' : 'block'; + this.isQueryCollapsed = !this.isQueryCollapsed + queryEditor.style.display = this.isQueryCollapsed ? 'none' : 'block' } } render() { - const { results, columns, response, errorMessage, currentQuery, runningQuery } = this.state; - const isQueryRunning = runningQuery !== undefined && response.stats !== undefined && (response.stats.state === 'RUNNING' || response.stats.state === 'QUEUED'); + const { results, columns, response, errorMessage, currentQuery, runningQuery } = this.state + const isQueryRunning = + runningQuery !== undefined && + response.stats !== undefined && + (response.stats.state === 'RUNNING' || response.stats.state === 'QUEUED') return ( <>
-
-
-
-
{ theme="vs-dark" value={currentQuery?.query || ''} options={{ - selectOnLineNumbers: true, - minimap: { enabled: isMaximized }, - // Add these options for better editing experience - formatOnPaste: true, - formatOnType: false, - autoIndent: 'full', + selectOnLineNumbers: true, + minimap: { enabled: isMaximized }, + // Add these options for better editing experience + formatOnPaste: true, + formatOnType: false, + autoIndent: 'full', }} onMount={this.editorDidMount} onChange={this.handleEditorChange} /> -
- - ); - } +
+ + ) + } } -export default QueryEditor; \ No newline at end of file +export default QueryEditor diff --git a/precise/src/ResultSet.tsx b/precise/src/ResultSet.tsx index b443960..46da4ac 100644 --- a/precise/src/ResultSet.tsx +++ b/precise/src/ResultSet.tsx @@ -1,26 +1,25 @@ import React from 'react' -import QueryInfo from './schema/QueryInfo'; -import ReactDOMServer from 'react-dom/server'; -import ErrorBox from './utils/ErrorBoxProvider'; -import ProgressBar from './utils/ProgressBar'; -import CopyLink from './utils/CopyLink'; -import ClearButton from './utils/ClearButton'; -import './style/results.css'; +import QueryInfo from './schema/QueryInfo' +import ReactDOMServer from 'react-dom/server' +import ErrorBox from './utils/ErrorBoxProvider' +import ProgressBar from './utils/ProgressBar' +import CopyLink from './utils/CopyLink' +import ClearButton from './utils/ClearButton' +import './style/results.css' interface ResultSetProps { - queryInfo: QueryInfo | undefined; - results: any[]; - columns: any[]; - response: any; - errorMessage: string; - onClearResults: (queryId: string | undefined) => void; + queryInfo: QueryInfo | undefined + results: any[] + columns: any[] + response: any + errorMessage: string + onClearResults: (queryId: string | undefined) => void } class ResultSet extends React.Component { - - previousRunningPercentage: number = 0; - statsHistory : any[] = []; - lastQueryId : string | undefined = undefined + previousRunningPercentage: number = 0 + statsHistory: any[] = [] + lastQueryId: string | undefined = undefined renderHeader(columns: any) { return ( @@ -33,23 +32,23 @@ class ResultSet extends React.Component { ))} - ); + ) } renderCell(cellData: any, cellIndex: any) { return ( - - {cellData == null ? "null" : cellData} + + {cellData == null ? 'null' : cellData} - ); + ) } renderRow(rowData: any, rowIndex: any) { return ( - {rowData.map((cellData : any, cellIndex: any) => this.renderCell(cellData, cellIndex))} + {rowData.map((cellData: any, cellIndex: any) => this.renderCell(cellData, cellIndex))} - ); + ) } renderPage(pageData: any, pageIndex: any) { @@ -58,209 +57,215 @@ class ResultSet extends React.Component { {pageData.map((rowData: any, rowIndex: any) => this.renderRow(rowData, rowIndex))} - ); + ) } - renderTable = (results: any[], response : any, columns : any) => { + renderTable = (results: any[], response: any, columns: any) => { return ( -
30 ? "scrollable" : "result-table-container"}> +
30 ? 'scrollable' : 'result-table-container'}> {this.renderInnerTable(results, response, columns)}
- ); + ) } - - renderInnerTable = (results: any[], response : any, columns : any) => { + + renderInnerTable = (results: any[], response: any, columns: any) => { return ( {columns ? this.renderHeader(columns) : null} - {results && results.length ? ( - results.map((pageData, pageIndex) => this.renderPage(pageData, pageIndex)) - ) : null} + {results && results.length + ? results.map((pageData, pageIndex) => this.renderPage(pageData, pageIndex)) + : null}
) } isFinishedFailedOrCancelled(state: string) { - return state === 'FINISHED' || state === 'FAILED' || state === 'CANCELLED'; + return state === 'FINISHED' || state === 'FAILED' || state === 'CANCELLED' } getRowCount() { - const { results } = this.props; + const { results } = this.props if (!results) { - return 0; + return 0 } - return results.reduce((sum, page) => sum + page.length, 0); + return results.reduce((sum, page) => sum + page.length, 0) } formatMillisAsHHMMSS(millis: number) { - const seconds = Math.floor(millis / 1000); - const minutes = Math.floor(seconds / 60); - const hours = Math.floor(minutes / 60); - return `${hours}h ${minutes % 60}m ${seconds % 60}s`; + const seconds = Math.floor(millis / 1000) + const minutes = Math.floor(seconds / 60) + const hours = Math.floor(minutes / 60) + return `${hours}h ${minutes % 60}m ${seconds % 60}s` } bytesToCorrectScale(bytes: number) { - if (isNaN(bytes)) { - return ``; + return `` } if (bytes < 128) { - return `${bytes.toFixed(0)} B`; + return `${bytes.toFixed(0)} B` } - const kb = bytes / 1024; + const kb = bytes / 1024 if (kb < 128) { - return `${kb.toFixed(2)} KB`; + return `${kb.toFixed(2)} KB` } - const mb = kb / 1024; + const mb = kb / 1024 if (mb < 128) { - return `${mb.toFixed(2)} MB`; + return `${mb.toFixed(2)} MB` } - const gb = mb / 1024; - return `${gb.toFixed(2)} GB`; + const gb = mb / 1024 + return `${gb.toFixed(2)} GB` } - unpackSubstages(subStages: any, stages : any[], depth: number) : any[] { + unpackSubstages(subStages: any, stages: any[], depth: number): any[] { if (subStages) { for (let i = 0; i < subStages.length; i++) { - const subStage = subStages[i]; - stages.push({ stage: subStage, depth: depth }); + const subStage = subStages[i] + stages.push({ stage: subStage, depth: depth }) if (subStage.subStages) { - this.unpackSubstages(subStage.subStages, stages, depth + 1); + this.unpackSubstages(subStage.subStages, stages, depth + 1) } } } - return stages; + return stages } rowCountToCorrectScale(rowCount: number) { - // if not a number return 0 if (isNaN(rowCount)) { - return 0; + return 0 } if (rowCount < 1000) { - return rowCount.toFixed(0); + return rowCount.toFixed(0) } - const k = rowCount / 1000; + const k = rowCount / 1000 if (k < 1000) { - return `${k.toFixed(1)}K`; + return `${k.toFixed(1)}K` } - const m = k / 1000; + const m = k / 1000 if (m < 1000) { - return `${m.toFixed(1)}M`; + return `${m.toFixed(1)}M` } - const b = m / 1000; - return `${b.toFixed(1)}B`; + const b = m / 1000 + return `${b.toFixed(1)}B` } // reset function reset() { - this.statsHistory = []; + this.statsHistory = [] } formatTableAsPlainText(results: any[], columns: any[]): string { if (!columns || columns.length === 0) { - return ''; + return '' } // Calculate the maximum width for each column - const columnWidths = columns.map((column: any) => - Math.max(column.name.length, ...results.flat().map((row: any[]) => - row[columns.indexOf(column)]?.toString().length || 0 - )) - ); + const columnWidths = columns.map((column: any) => + Math.max( + column.name.length, + ...results.flat().map((row: any[]) => row[columns.indexOf(column)]?.toString().length || 0) + ) + ) // Create the header - let tableText = columns.map((column: any, index: number) => - column.name.padEnd(columnWidths[index]) - ).join(' | ') + '\n'; + let tableText = + columns.map((column: any, index: number) => column.name.padEnd(columnWidths[index])).join(' | ') + '\n' // Add a separator line - tableText += columnWidths.map(width => '-'.repeat(width)).join('-+-') + '\n'; + tableText += columnWidths.map((width) => '-'.repeat(width)).join('-+-') + '\n' // Add the data rows results.forEach((page: any[]) => { page.forEach((row: any[]) => { - tableText += row.map((cell: any, index: number) => - (cell?.toString() || '').padEnd(columnWidths[index]) - ).join(' | ') + '\n'; - }); - }); + tableText += + row + .map((cell: any, index: number) => (cell?.toString() || '').padEnd(columnWidths[index])) + .join(' | ') + '\n' + }) + }) - return tableText; + return tableText } copy() { - const { results, columns } = this.props; - const htmlContent = ReactDOMServer.renderToString( - this.renderInnerTable(results, this.props.response, columns) - ); - const plainTextContent = this.formatTableAsPlainText(results, columns); - - const blobHtml = new Blob([htmlContent], { type: 'text/html' }); - const blobText = new Blob([plainTextContent], { type: 'text/plain' }); - - navigator.clipboard.write([ - new ClipboardItem({ - 'text/html': blobHtml, - 'text/plain': blobText, + const { results, columns } = this.props + const htmlContent = ReactDOMServer.renderToString(this.renderInnerTable(results, this.props.response, columns)) + const plainTextContent = this.formatTableAsPlainText(results, columns) + + const blobHtml = new Blob([htmlContent], { type: 'text/html' }) + const blobText = new Blob([plainTextContent], { type: 'text/plain' }) + + navigator.clipboard + .write([ + new ClipboardItem({ + 'text/html': blobHtml, + 'text/plain': blobText, + }), + ]) + .then(() => { + console.log('Table copied successfully') + }) + .catch((err) => { + console.error('Failed to copy table:', err) }) - ]).then(() => { - console.log('Table copied successfully'); - }).catch(err => { - console.error('Failed to copy table:', err); - }); } render() { - const { queryInfo, results, columns, response, errorMessage } = this.props; + const { queryInfo, results, columns, response, errorMessage } = this.props // if the query ID has changed, reset the last processed rows and elapsed time if (queryInfo == null || this.lastQueryId !== queryInfo.id) { - this.reset(); + this.reset() } - - this.lastQueryId = queryInfo?.id; + + this.lastQueryId = queryInfo?.id // new implementation, look over 10 second window in stats history - var processedRowsSinceLast = 0; + let processedRowsSinceLast = 0 if (response.stats && response.stats.processedRows) { - const stat = response.stats; - this.statsHistory.push(stat); + const stat = response.stats + this.statsHistory.push(stat) if (this.statsHistory.length > 2) { - const lastStat = this.statsHistory[this.statsHistory.length - 1]; - const tenSecondsAgo = lastStat.elapsedTimeMillis - 10000; + const lastStat = this.statsHistory[this.statsHistory.length - 1] + const tenSecondsAgo = lastStat.elapsedTimeMillis - 10000 // walk backwards to find stat that is 10 seconds ago - let indexOfFirstStatInWindow = 0; + let indexOfFirstStatInWindow = 0 for (let i = this.statsHistory.length - 2; i >= 0; i--) { if (this.statsHistory[i].elapsedTimeMillis < tenSecondsAgo) { - indexOfFirstStatInWindow = i; - break; + indexOfFirstStatInWindow = i + break } } - const firstStatInWindow = this.statsHistory[indexOfFirstStatInWindow]; - processedRowsSinceLast = (lastStat.processedRows - firstStatInWindow.processedRows) / ((lastStat.elapsedTimeMillis - firstStatInWindow.elapsedTimeMillis) / 1000.0); + const firstStatInWindow = this.statsHistory[indexOfFirstStatInWindow] + processedRowsSinceLast = + (lastStat.processedRows - firstStatInWindow.processedRows) / + ((lastStat.elapsedTimeMillis - firstStatInWindow.elapsedTimeMillis) / 1000.0) } } // unpack substages into a list of stages with nest depth - var stages : any[] = []; - this.unpackSubstages(response.stats && response.stats.rootStage && response.stats.rootStage.subStages, stages, 1); + const stages: any[] = [] + this.unpackSubstages( + response.stats && response.stats.rootStage && response.stats.rootStage.subStages, + stages, + 1 + ) // get min and max throughput - var minThroughput = Number.MAX_VALUE; - var maxThroughput = 0; + let minThroughput = Number.MAX_VALUE + let maxThroughput = 0 for (let i = 0; i < stages.length; i++) { - const stage = stages[i].stage; - const throughput = stage.processedRows / (stage.wallTimeMillis / 1000); + const stage = stages[i].stage + const throughput = stage.processedRows / (stage.wallTimeMillis / 1000) if (throughput < minThroughput) { - minThroughput = throughput; + minThroughput = throughput } if (throughput > maxThroughput) { - maxThroughput = throughput; + maxThroughput = throughput } } @@ -269,45 +274,66 @@ class ResultSet extends React.Component {
{ // only return if there are columns - (columns && columns.length ? ( + columns && columns.length ? (
{response.stats && this.isFinishedFailedOrCancelled(response.stats.state) && ( this.props.onClearResults(queryInfo?.id)} /> )} this.copy()} /> {/* if row count > 30 place in scrollable div */} - { this.renderTable(results, response, columns) } -
- ) : null) - } - { - (response && response.id ? ( -
- {this.getRowCount()} rows: - {response.id} - + {this.renderTable(results, response, columns)}
- ) : null) - } - { - (errorMessage ? ( - - ) : null) + ) : null } + {response && response.id ? ( +
+ {this.getRowCount()} rows:{' '} + + {response.id} + +
+ ) : null} + {errorMessage ? : null} {/* if the status is not finished, show spinner */} - {response && response.stats && response.stats.state !== 'FINISHED' && response.stats.state !== 'FAILED' && response.stats.state !== 'CANCELLED' ? ( + {response && + response.stats && + response.stats.state !== 'FINISHED' && + response.stats.state !== 'FAILED' && + response.stats.state !== 'CANCELLED' ? (
-
{response && response.stats && response.stats.runningPercentage ? Math.floor(response.stats.runningPercentage) : 0}%
-
{response.stats.state}
Workers: {response.stats.nodes}, Running splits: {response.stats.runningSplits}, Total splits: {response.stats.totalSplits}, Run time: {Math.floor(response.stats.elapsedTimeMillis / 1000)}s
+
+ {response && response.stats && response.stats.runningPercentage + ? Math.floor(response.stats.runningPercentage) + : 0} + % +
+
+ {response.stats.state} +
+ Workers: {response.stats.nodes}, Running splits: {response.stats.runningSplits}, + Total splits: {response.stats.totalSplits}, Run time:{' '} + {Math.floor(response.stats.elapsedTimeMillis / 1000)}s +
- -
{this.formatMillisAsHHMMSS(response.stats.elapsedTimeMillis)}
+ +
+ {this.formatMillisAsHHMMSS(response.stats.elapsedTimeMillis)} +
{/* if response.stats.subStages */} @@ -315,12 +341,20 @@ class ResultSet extends React.Component {
- {/* groupings for subcategories of metrics */} + + {' '} + {/* groupings for subcategories of metrics */} - - - + + + @@ -338,49 +372,127 @@ class ResultSet extends React.Component { - + - - + + - - + + - - - - + + + + {/* look at all the substages in subStages */} {stages.map((subStageInfo: any) => { return ( - - + + - - + + - - + + - - - - + + + + - ); + ) })} - + - - - - - + + + + + - + @@ -392,8 +504,8 @@ class ResultSet extends React.Component { ) : null} - ); + ) } } -export default ResultSet; \ No newline at end of file +export default ResultSet diff --git a/precise/src/SubstitutionEditor.tsx b/precise/src/SubstitutionEditor.tsx index 2a8aaed..da81681 100644 --- a/precise/src/SubstitutionEditor.tsx +++ b/precise/src/SubstitutionEditor.tsx @@ -1,77 +1,81 @@ -import React, { useState, useEffect } from 'react'; +import React, { useState, useEffect } from 'react' interface SubstitutionField { - name: string; - type: 'varchar' | 'bigint' | 'double'; - defaultValue?: string; - options?: string[]; // TODO: Implement options retrieval + name: string + type: 'varchar' | 'bigint' | 'double' + defaultValue?: string + options?: string[] // TODO: Implement options retrieval } interface SubstitutionEditorProps { - query: string; - onSubstitutionChange: (substitutions: Record) => void; + query: string + onSubstitutionChange: (substitutions: Record) => void } const SubstitutionEditor: React.FC = ({ query, onSubstitutionChange }) => { - const [fields, setFields] = useState([]); - const [values, setValues] = useState>({}); + const [fields, setFields] = useState([]) + const [values, setValues] = useState>({}) - useEffect(() => { - const extractedFields = extractSubstitutionFields(query); - setFields(extractedFields); - const initialValues = extractedFields.reduce((acc, field) => { - acc[field.name] = field.defaultValue || ''; - return acc; - }, {} as Record); - setValues(initialValues); - onSubstitutionChange(initialValues); - }, [query]); + useEffect(() => { + const extractedFields = extractSubstitutionFields(query) + setFields(extractedFields) + const initialValues = extractedFields.reduce( + (acc, field) => { + acc[field.name] = field.defaultValue || '' + return acc + }, + {} as Record + ) + setValues(initialValues) + onSubstitutionChange(initialValues) + }, [query, onSubstitutionChange]) - const handleInputChange = (name: string, value: string) => { - setValues(prev => { - const newValues = { ...prev, [name]: value }; - onSubstitutionChange(newValues); - return newValues; - }); - }; + const handleInputChange = (name: string, value: string) => { + setValues((prev) => { + const newValues = { ...prev, [name]: value } + onSubstitutionChange(newValues) + return newValues + }) + } - const extractSubstitutionFields = (query: string): SubstitutionField[] => { - const regex = /\/\*\s*\{\{\s*([A-z\s0-9]+)(?::(varchar|bigint|double)?(?::([a-zA-Z0-9\.-_\s]+)?)?)?\s*\}\}\s*\*\//g; - const fields: SubstitutionField[] = []; - let match; + const extractSubstitutionFields = (query: string): SubstitutionField[] => { + const regex = + /\/\*\s*\{\{\s*([A-z\s0-9]+)(?::(varchar|bigint|double)?(?::([a-zA-Z0-9.-_\s]+)?)?)?\s*\}\}\s*\*\//g + const fields: SubstitutionField[] = [] + let match - while ((match = regex.exec(query)) !== null) { - fields.push({ - name: match[1], - type: (match[2] as 'varchar' | 'bigint' | 'double') || 'varchar', - defaultValue: match[3], - }); - } + while ((match = regex.exec(query)) !== null) { + fields.push({ + name: match[1], + type: (match[2] as 'varchar' | 'bigint' | 'double') || 'varchar', + defaultValue: match[3], + }) + } - return fields; - }; + return fields + } - if (fields.length === 0) { - return null; - } + if (fields.length === 0) { + return null + } - return ( -
-

Query Parameters

- {fields.map((field) => ( -
- - handleInputChange(field.name, e.target.value)} - placeholder={field.defaultValue} - /> + return ( +
+

Query Parameters

+ {fields.map((field) => ( +
+ + handleInputChange(field.name, e.target.value)} + placeholder={field.defaultValue} + /> +
+ ))}
- ))} -
- ); -}; + ) +} -export default SubstitutionEditor; \ No newline at end of file +export default SubstitutionEditor diff --git a/precise/src/Utils.tsx b/precise/src/Utils.tsx index 1ffae5f..69c5477 100644 --- a/precise/src/Utils.tsx +++ b/precise/src/Utils.tsx @@ -1,7 +1,7 @@ export function generateGUID(): string { - return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { - var r = Math.random() * 16 | 0, - v = c === 'x' ? r : (r & 0x3 | 0x8); - return v.toString(16); - }); - } \ No newline at end of file + return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { + const r = (Math.random() * 16) | 0, + v = c === 'x' ? r : (r & 0x3) | 0x8 + return v.toString(16) + }) +} diff --git a/precise/src/controls/catalog_viewer/CatalogViewer.tsx b/precise/src/controls/catalog_viewer/CatalogViewer.tsx index d9d6c08..d118f61 100644 --- a/precise/src/controls/catalog_viewer/CatalogViewer.tsx +++ b/precise/src/controls/catalog_viewer/CatalogViewer.tsx @@ -1,63 +1,56 @@ -import React, { useState, useEffect, useCallback, useRef } from 'react'; -import SchemaProvider from './../../sql/SchemaProvider'; -import Catalog from './../../schema/Catalog'; -import TableReference from '../../schema/TableReference'; -import CatalogViewerSchema from './CatalogViewerSchema'; -import ErrorBox from './../../utils/ErrorBoxProvider'; -import CloseIcon from '../../assets/close.png'; -import { ViewerStateManager, buildPath } from './ViewerState'; -import './catalogviewer.css'; -import { Loader2, ChevronRight } from 'lucide-react'; +import React, { useState, useEffect, useCallback, useRef } from 'react' +import SchemaProvider from './../../sql/SchemaProvider' +import Catalog from './../../schema/Catalog' +import TableReference from '../../schema/TableReference' +import CatalogViewerSchema from './CatalogViewerSchema' +import ErrorBox from './../../utils/ErrorBoxProvider' +import CloseIcon from '../../assets/close.png' +import { ViewerStateManager, buildPath } from './ViewerState' +import './catalogviewer.css' +import { Loader2, ChevronRight } from 'lucide-react' interface CatalogViewerProps { - initialFilterText?: string; - onGenerateQuery?: (query: string, catalog?: string, schema?: string) => void; - onAppendQuery?: (query: string, catalog?: string, schema?: string) => void; + initialFilterText?: string + onGenerateQuery?: (query: string, catalog?: string, schema?: string) => void + onAppendQuery?: (query: string, catalog?: string, schema?: string) => void } -const CatalogViewer: React.FC = ({ - initialFilterText = '', - onGenerateQuery, - onAppendQuery -}) => { +const CatalogViewer: React.FC = ({ initialFilterText = '', onGenerateQuery, onAppendQuery }) => { // Basic state - const [catalogs, setCatalogs] = useState>(new Map()); - const [errorMessage, setErrorMessage] = useState(); - const [filterText, setFilterText] = useState(initialFilterText); - const [debouncedFilterText, setDebouncedFilterText] = useState(initialFilterText); - const [isLoading, setIsLoading] = useState(false); - const [searchColumns, setSearchColumns] = useState(false); + const [catalogs, setCatalogs] = useState>(new Map()) + const [errorMessage, setErrorMessage] = useState() + const [filterText, setFilterText] = useState(initialFilterText) + const [debouncedFilterText, setDebouncedFilterText] = useState(initialFilterText) + const [isLoading, setIsLoading] = useState(false) + const [searchColumns, setSearchColumns] = useState(false) // View state - const [matches, setMatches] = useState>(new Set()); - const [expandedNodes, setExpandedNodes] = useState>(new Set()); - const viewerState = useRef(); + const [matches, setMatches] = useState>(new Set()) + const [expandedNodes, setExpandedNodes] = useState>(new Set()) + const viewerState = useRef() - const [isLoadingColumns, setIsLoadingColumns] = useState(false); + const [isLoadingColumns, setIsLoadingColumns] = useState(false) // Initialize viewer state manager useEffect(() => { - viewerState.current = new ViewerStateManager( - (update) => { + viewerState.current = new ViewerStateManager((update) => { console.log('State update received:', { matches: update.matches.size, - expanded: update.expandedNodes.size - }); - setMatches(update.matches); - setExpandedNodes(update.expandedNodes); - }, - setIsLoadingColumns - ); - }, []); + expanded: update.expandedNodes.size, + }) + setMatches(update.matches) + setExpandedNodes(update.expandedNodes) + }, setIsLoadingColumns) + }, []) // Handle filter changes useEffect(() => { const timer = setTimeout(() => { - setDebouncedFilterText(filterText); - }, 300); + setDebouncedFilterText(filterText) + }, 300) - return () => clearTimeout(timer); - }, [filterText]); + return () => clearTimeout(timer) + }, [filterText]) // Apply search when filter or search options change useEffect(() => { @@ -65,103 +58,99 @@ const CatalogViewer: React.FC = ({ console.log('Starting new search:', { filter: debouncedFilterText, searchColumns, - catalogCount: catalogs.size - }); - viewerState.current.startSearch(debouncedFilterText, searchColumns, catalogs); + catalogCount: catalogs.size, + }) + viewerState.current.startSearch(debouncedFilterText, searchColumns, catalogs) } - }, [debouncedFilterText, searchColumns, catalogs]); + }, [debouncedFilterText, searchColumns, catalogs]) const loadCatalogs = useCallback(async () => { - setIsLoading(true); - setErrorMessage(undefined); - + setIsLoading(true) + setErrorMessage(undefined) + try { await SchemaProvider.populateCatalogsAndRefreshTableList( () => { - setCatalogs(SchemaProvider.catalogs); - setIsLoading(false); + setCatalogs(SchemaProvider.catalogs) + setIsLoading(false) }, (error: string) => { - setErrorMessage(error); - setIsLoading(false); + setErrorMessage(error) + setIsLoading(false) } - ); + ) } catch (error) { - setErrorMessage(error instanceof Error ? error.message : 'An unknown error occurred'); - setIsLoading(false); + setErrorMessage(error instanceof Error ? error.message : 'An unknown error occurred') + setIsLoading(false) } - }, []); + }, []) const handleToggle = async (path: string) => { - if (!viewerState.current) return; - + if (!viewerState.current) return + // Toggle the expansion state - viewerState.current.toggleExpanded(path); + viewerState.current.toggleExpanded(path) // If it's a table path, ensure data is loaded - const pathParts = path.split('.'); + const pathParts = path.split('.') if (pathParts.length === 3) { - const tableRef = new TableReference( - pathParts[0], - pathParts[1], - pathParts[2] - ); - + const tableRef = new TableReference(pathParts[0], pathParts[1], pathParts[2]) + await new Promise((resolve) => { SchemaProvider.getTableWithCache(tableRef, () => { - resolve(); - }); - }); + resolve() + }) + }) } - }; + } useEffect(() => { - loadCatalogs(); - }, [loadCatalogs]); + loadCatalogs() + }, [loadCatalogs]) - const isVisible = (path: string): boolean => - viewerState.current?.isVisible(path) ?? true; + const isVisible = (path: string): boolean => viewerState.current?.isVisible(path) ?? true - const isExpanded = (path: string): boolean => - viewerState.current?.isExpanded(path) ?? false; + const isExpanded = (path: string): boolean => viewerState.current?.isExpanded(path) ?? false - const hasMatchingChildren = (path: string): boolean => - viewerState.current?.hasMatchingChildren(path) ?? false; + const hasMatchingChildren = (path: string): boolean => viewerState.current?.hasMatchingChildren(path) ?? false // Generate query handler const handleGenerateQuery = ( - queryType: string, - tableRef: TableReference | null, - catalogName?: string, + queryType: string, + tableRef: TableReference | null, + catalogName?: string, schemaName?: string ) => { if (!onAppendQuery) { - console.warn('No query handler available'); - return; + console.warn('No query handler available') + return } - + if (queryType === 'SELECT' && tableRef) { // Load table first to get columns - SchemaProvider.getTableWithCache(tableRef, (table : any) => { - const columns = table.getColumns().map((col: { getName: () => string }) => col.getName()).join(',\n '); - const query = `SELECT\n ${columns}\nFROM ${tableRef.catalogName}.${tableRef.schemaName}.${tableRef.tableName}\nlimit 100`; - onAppendQuery(query, tableRef.catalogName, tableRef.schemaName); - }); + SchemaProvider.getTableWithCache(tableRef, (table: any) => { + const columns = table + .getColumns() + .map((col: { getName: () => string }) => col.getName()) + .join(',\n ') + const query = `SELECT\n ${columns}\nFROM ${tableRef.catalogName}.${tableRef.schemaName}.${tableRef.tableName}\nlimit 100` + onAppendQuery(query, tableRef.catalogName, tableRef.schemaName) + }) } else if (queryType === 'SET_SCHEMA' && catalogName && schemaName) { // Just set the catalog and schema - onAppendQuery('', catalogName, schemaName); + onAppendQuery('', catalogName, schemaName) } - }; + } const handleGenerateCatalogQuery = (e: React.MouseEvent, catalogName: string) => { - e.stopPropagation(); // Prevent toggling expansion - + e.stopPropagation() // Prevent toggling expansion + if (onGenerateQuery) { - onGenerateQuery('', catalogName, ''); + onGenerateQuery('', catalogName, '') } else { - console.warn('No query handler available'); + console.warn('No query handler available') } - }; + } return (
@@ -175,35 +164,28 @@ const CatalogViewer: React.FC = ({ className="filter-input" /> {filterText && ( -
setFilterText('')} role="button" aria-label="Clear search" > - Clear + Clear
)}
- - -
{isLoading &&
Loading catalogs...
} - - {errorMessage && ( - - )} + + {errorMessage && }
{Array.from(catalogs.values()) .sort((a, b) => a.getName().localeCompare(b.getName())) .map((catalog: Catalog) => { - const catalogName = catalog.getName(); - const catalogPath = buildPath.catalog(catalogName); - + const catalogName = catalog.getName() + const catalogPath = buildPath.catalog(catalogName) + if (filterText && !isVisible(catalogPath)) { - return null; + return null } return ( @@ -237,35 +214,31 @@ const CatalogViewer: React.FC = ({ {catalogName} {catalog.getType()} catalog
- + {isExpanded(catalogPath) ? '▼' : '▶'} -
handleGenerateCatalogQuery(e, catalogName)} title="Set this catalog as default catalog" >
- + {isExpanded(catalogPath) && (
{catalog.getError() && ( - + )} {Array.from(catalog.getSchemas().values()) .sort((a, b) => a.getName().localeCompare(b.getName())) - .map(schema => { - const schemaPath = buildPath.schema( - catalogName, - schema.getName() - ); - + .map((schema) => { + const schemaPath = buildPath.schema(catalogName, schema.getName()) + return ( = ({ onToggle={handleToggle} onGenerateQuery={handleGenerateQuery} /> - ); + ) })}
)} - ); - }) - } + ) + })} - ); -}; + ) +} -export default CatalogViewer; \ No newline at end of file +export default CatalogViewer diff --git a/precise/src/controls/catalog_viewer/CatalogViewerColumn.tsx b/precise/src/controls/catalog_viewer/CatalogViewerColumn.tsx index 7fda8f2..201510b 100644 --- a/precise/src/controls/catalog_viewer/CatalogViewerColumn.tsx +++ b/precise/src/controls/catalog_viewer/CatalogViewerColumn.tsx @@ -1,16 +1,16 @@ -import React, { useState, useEffect } from 'react'; -import Column from '../../schema/Column'; -import TableReference from './../../schema/TableReference'; -import CopyLink from './../../utils/CopyLink'; -import { buildPath } from './ViewerState'; -import './catalogviewer.css'; +import React, { useState, useEffect } from 'react' +import Column from '../../schema/Column' +import TableReference from './../../schema/TableReference' +import CopyLink from './../../utils/CopyLink' +import { buildPath } from './ViewerState' +import './catalogviewer.css' interface CatalogViewerColumnProps { - tableRef: TableReference; - column: Column; - isExpanded: boolean; - isVisible: (path: string) => boolean; - onToggle: (path: string) => Promise; + tableRef: TableReference + column: Column + isExpanded: boolean + isVisible: (path: string) => boolean + onToggle: (path: string) => Promise } const CatalogViewerColumn: React.FC = ({ @@ -18,49 +18,44 @@ const CatalogViewerColumn: React.FC = ({ column, isExpanded, isVisible, - onToggle + onToggle, }) => { - const [sampleValues, setSampleValues] = useState([]); - const [isLoadingSamples, setIsLoadingSamples] = useState(false); - const [showSamples, setShowSamples] = useState(false); + const [sampleValues, setSampleValues] = useState([]) + const [isLoadingSamples, setIsLoadingSamples] = useState(false) + const [showSamples, setShowSamples] = useState(false) - const columnPath = buildPath.column( - tableRef.catalogName, - tableRef.schemaName, - tableRef.tableName, - column.getName() - ); + const columnPath = buildPath.column(tableRef.catalogName, tableRef.schemaName, tableRef.tableName, column.getName()) // Load sample values when samples are shown useEffect(() => { if (showSamples && sampleValues.length === 0 && !isLoadingSamples) { - setIsLoadingSamples(true); + setIsLoadingSamples(true) column.getSampleValues(tableRef, (newSampleValues: string[]) => { - setSampleValues(newSampleValues); - setIsLoadingSamples(false); - }); + setSampleValues(newSampleValues) + setIsLoadingSamples(false) + }) } - }, [showSamples, column, tableRef, sampleValues.length, isLoadingSamples]); + }, [showSamples, column, tableRef, sampleValues.length, isLoadingSamples]) const handleRefresh = (e: React.MouseEvent) => { - e.stopPropagation(); - setIsLoadingSamples(true); + e.stopPropagation() + setIsLoadingSamples(true) column.getSampleValues(tableRef, (newSampleValues: string[]) => { - setSampleValues(newSampleValues); - setIsLoadingSamples(false); - }); - }; + setSampleValues(newSampleValues) + setIsLoadingSamples(false) + }) + } const handleToggle = async () => { // Toggle samples if already expanded if (isExpanded) { - setShowSamples(!showSamples); + setShowSamples(!showSamples) } else { // Just expand the column initially - await onToggle(columnPath); - setShowSamples(true); + await onToggle(columnPath) + setShowSamples(true) } - }; + } const renderSampleValue = (value: string, index: number) => { if (value === null) { @@ -68,51 +63,36 @@ const CatalogViewerColumn: React.FC = ({
NULL
- ); + ) } - - const displayValue = value.length > 50 - ? value.substring(0, 50) + "..." - : value; - + + const displayValue = value.length > 50 ? value.substring(0, 50) + '...' : value + return (
{displayValue} - navigator.clipboard.writeText(value)} - /> + navigator.clipboard.writeText(value)} />
- ); - }; + ) + } // Check visibility using the passed down helper if (!isVisible(columnPath)) { - return null; + return null } return (
{showSamples && ( -
-
- ⟳ -
+
+
)} -
+
{column.getName()} {column.getType()} - - {showSamples ? '▼' : '▶'} - + {showSamples ? '▼' : '▶'}
{column.getExtraOrComment() && (
@@ -132,15 +112,13 @@ const CatalogViewerColumn: React.FC = ({ ) : sampleValues.length === 0 ? (
No sample values available
) : ( - sampleValues.map((value, index) => - renderSampleValue(value, index) - ) + sampleValues.map((value, index) => renderSampleValue(value, index)) )}
)}
- ); -}; + ) +} -export default CatalogViewerColumn; \ No newline at end of file +export default CatalogViewerColumn diff --git a/precise/src/controls/catalog_viewer/CatalogViewerSchema.tsx b/precise/src/controls/catalog_viewer/CatalogViewerSchema.tsx index b2d131f..90857b4 100644 --- a/precise/src/controls/catalog_viewer/CatalogViewerSchema.tsx +++ b/precise/src/controls/catalog_viewer/CatalogViewerSchema.tsx @@ -1,63 +1,66 @@ -import React from 'react'; -import Schema from '../../schema/Schema'; -import Table from '../../schema/Table'; -import CatalogViewerTable from './CatalogViewerTable'; -import TableReference from '../../schema/TableReference'; -import { buildPath } from './ViewerState'; -import './catalogviewer.css'; -import { ChevronRight } from 'lucide-react'; +import React from 'react' +import Schema from '../../schema/Schema' +import Table from '../../schema/Table' +import CatalogViewerTable from './CatalogViewerTable' +import TableReference from '../../schema/TableReference' +import { buildPath } from './ViewerState' +import './catalogviewer.css' +import { ChevronRight } from 'lucide-react' interface SchemaProps { - catalogName: string; - schema: Schema; - filterText: string; - isExpanded: boolean; - isVisible: (path: string) => boolean; - hasMatchingChildren: (path: string) => boolean; - onToggle: (path: string) => Promise; - onGenerateQuery?: (queryType: string, tableRef: TableReference | null, catalogName?: string, schemaName?: string) => void; + catalogName: string + schema: Schema + filterText: string + isExpanded: boolean + isVisible: (path: string) => boolean + hasMatchingChildren: (path: string) => boolean + onToggle: (path: string) => Promise + onGenerateQuery?: ( + queryType: string, + tableRef: TableReference | null, + catalogName?: string, + schemaName?: string + ) => void } -const CatalogViewerSchema: React.FC = ({ - catalogName, - schema, +const CatalogViewerSchema: React.FC = ({ + catalogName, + schema, filterText, isExpanded, isVisible, hasMatchingChildren, onToggle, - onGenerateQuery + onGenerateQuery, }) => { - const schemaPath = buildPath.schema(catalogName, schema.getName()); + const schemaPath = buildPath.schema(catalogName, schema.getName()) // Check visibility using the passed down helper if (filterText && !isVisible(schemaPath)) { - return null; + return null } const handleGenerateQuery = (e: React.MouseEvent) => { - e.stopPropagation(); // Prevent toggling expansion + e.stopPropagation() // Prevent toggling expansion if (onGenerateQuery) { - onGenerateQuery('SET_SCHEMA', null, catalogName, schema.getName()); + onGenerateQuery('SET_SCHEMA', null, catalogName, schema.getName()) } - }; + } return (
-
onToggle(schemaPath)} - > +
onToggle(schemaPath)}> {schema.getName()} schema - + {isExpanded ? '▼' : '▶'} - + {onGenerateQuery && ( -
@@ -69,25 +72,15 @@ const CatalogViewerSchema: React.FC = ({ {isExpanded && (
{Array.from(schema.getTables().values()) - .sort((a: Table, b: Table) => - a.getName().localeCompare(b.getName()) - ) + .sort((a: Table, b: Table) => a.getName().localeCompare(b.getName())) .map((table: Table) => { - const tablePath = buildPath.table( - catalogName, - schema.getName(), - table.getName() - ); + const tablePath = buildPath.table(catalogName, schema.getName(), table.getName()) // Table visibility is handled within the component return ( - = ({ onToggle={onToggle} onGenerateQuery={onGenerateQuery} /> - ); - }) - } + ) + })}
)}
- ); -}; + ) +} -export default CatalogViewerSchema; \ No newline at end of file +export default CatalogViewerSchema diff --git a/precise/src/controls/catalog_viewer/CatalogViewerTable.tsx b/precise/src/controls/catalog_viewer/CatalogViewerTable.tsx index 0f7a730..9283651 100644 --- a/precise/src/controls/catalog_viewer/CatalogViewerTable.tsx +++ b/precise/src/controls/catalog_viewer/CatalogViewerTable.tsx @@ -1,20 +1,20 @@ -import React, { useState, useEffect } from 'react'; -import Table from '../../schema/Table'; -import SchemaProvider from '../../sql/SchemaProvider'; -import TableReference from '../../schema/TableReference'; -import CatalogViewerColumn from './CatalogViewerColumn'; -import { buildPath } from './ViewerState'; -import './catalogviewer.css'; -import { ChevronRight } from 'lucide-react'; +import React, { useState, useEffect } from 'react' +import Table from '../../schema/Table' +import SchemaProvider from '../../sql/SchemaProvider' +import TableReference from '../../schema/TableReference' +import CatalogViewerColumn from './CatalogViewerColumn' +import { buildPath } from './ViewerState' +import './catalogviewer.css' +import { ChevronRight } from 'lucide-react' interface CatalogViewerTableProps { - tableRef: TableReference; - filterText: string; - isExpanded: boolean; - isVisible: (path: string) => boolean; - hasMatchingChildren: (path: string) => boolean; - onToggle: (path: string) => Promise; - onGenerateQuery?: (queryType: string, tableRef: TableReference) => void; + tableRef: TableReference + filterText: string + isExpanded: boolean + isVisible: (path: string) => boolean + hasMatchingChildren: (path: string) => boolean + onToggle: (path: string) => Promise + onGenerateQuery?: (queryType: string, tableRef: TableReference) => void } const CatalogViewerTable: React.FC = ({ @@ -23,19 +23,13 @@ const CatalogViewerTable: React.FC = ({ isExpanded, isVisible, onToggle, - onGenerateQuery + onGenerateQuery, }) => { - const [table, setTable] = useState
RowsBytesSplits + Rows + + Bytes + + Splits +
Stage
0 (root) {response.stats.rootStage.nodes}{this.rowCountToCorrectScale(response.stats.rootStage.processedRows)}{this.rowCountToCorrectScale(response.stats.rootStage.processedRows / (response.stats.rootStage.wallTimeMillis / 1000))} + {this.rowCountToCorrectScale(response.stats.rootStage.processedRows)} + + {this.rowCountToCorrectScale( + response.stats.rootStage.processedRows / + (response.stats.rootStage.wallTimeMillis / 1000) + )} + {this.bytesToCorrectScale(response.stats.rootStage.processedBytes)}{this.bytesToCorrectScale(response.stats.rootStage.processedBytes / (response.stats.wallTimeMillis / 1000))} + {this.bytesToCorrectScale(response.stats.rootStage.processedBytes)} + + {this.bytesToCorrectScale( + response.stats.rootStage.processedBytes / + (response.stats.wallTimeMillis / 1000) + )} + {this.bytesToCorrectScale(response.stats.rootStage.physicalInputBytes)}{response.stats.rootStage.queuedSplits}{response.stats.rootStage.runningSplits}{response.stats.rootStage.completedSplits} + {this.bytesToCorrectScale(response.stats.rootStage.physicalInputBytes)} + + {response.stats.rootStage.queuedSplits} + + {response.stats.rootStage.runningSplits} + + {response.stats.rootStage.completedSplits} +
{subStageInfo.stage.stageId}
+ {subStageInfo.stage.stageId} + {subStageInfo.stage.nodes}{this.rowCountToCorrectScale(subStageInfo.stage.processedRows)}{this.rowCountToCorrectScale(subStageInfo.stage.processedRows / (subStageInfo.stage.wallTimeMillis / 1000))} + {this.rowCountToCorrectScale(subStageInfo.stage.processedRows)} + + {this.rowCountToCorrectScale( + subStageInfo.stage.processedRows / + (subStageInfo.stage.wallTimeMillis / 1000) + )} + {this.bytesToCorrectScale(response.stats.processedBytes)}{this.bytesToCorrectScale(subStageInfo.stage.processedBytes / (subStageInfo.stage.wallTimeMillis / 1000))} + {this.bytesToCorrectScale(response.stats.processedBytes)} + + {this.bytesToCorrectScale( + subStageInfo.stage.processedBytes / + (subStageInfo.stage.wallTimeMillis / 1000) + )} + {this.bytesToCorrectScale(response.stats.physicalInputBytes)}{subStageInfo.stage.queuedSplits}{subStageInfo.stage.runningSplits}{subStageInfo.stage.completedSplits} + {this.bytesToCorrectScale(response.stats.physicalInputBytes)} + + {subStageInfo.stage.queuedSplits} + + {subStageInfo.stage.runningSplits} + + {subStageInfo.stage.completedSplits} +
Total {response.stats.nodes}{this.rowCountToCorrectScale(response.stats.processedRows)}{this.rowCountToCorrectScale(response.stats.processedRows / (response.stats.elapsedTimeMillis / 1000))}{this.rowCountToCorrectScale(processedRowsSinceLast)}{this.bytesToCorrectScale(response.stats.processedBytes)}{this.bytesToCorrectScale(response.stats.processedBytes / (response.stats.elapsedTimeMillis / 1000))} + {this.rowCountToCorrectScale(response.stats.processedRows)} + + {this.rowCountToCorrectScale( + response.stats.processedRows / + (response.stats.elapsedTimeMillis / 1000) + )} + + {this.rowCountToCorrectScale(processedRowsSinceLast)} + + {this.bytesToCorrectScale(response.stats.processedBytes)} + + {this.bytesToCorrectScale( + response.stats.processedBytes / + (response.stats.elapsedTimeMillis / 1000) + )} + {this.bytesToCorrectScale(response.stats.physicalInputBytes)} + {this.bytesToCorrectScale(response.stats.physicalInputBytes)} + {response.stats.queuedSplits} {response.stats.runningSplits} {response.stats.completedSplits}
(() => - new Table(tableRef.tableName) - ); + const [table, setTable] = useState
(() => new Table(tableRef.tableName)) - const [isLoadingColumns, setIsLoadingColumns] = useState(false); + const [isLoadingColumns, setIsLoadingColumns] = useState(false) - const tablePath = buildPath.table( - tableRef.catalogName, - tableRef.schemaName, - tableRef.tableName - ); + const tablePath = buildPath.table(tableRef.catalogName, tableRef.schemaName, tableRef.tableName) // Load columns when expanded OR when there's an active filter useEffect(() => { @@ -43,47 +37,42 @@ const CatalogViewerTable: React.FC = ({ console.log(`Loading table data for ${tableRef.tableName}`, { isExpanded, filterText, - hasColumns: table.getColumns().length > 0 - }); + hasColumns: table.getColumns().length > 0, + }) - table.setLoading(true); + table.setLoading(true) SchemaProvider.getTableWithCache(tableRef, (loadedTable: Table) => { console.log(`Table data loaded for ${tableRef.tableName}`, { - columnCount: loadedTable.getColumns().length - }); - setTable(loadedTable); - }); + columnCount: loadedTable.getColumns().length, + }) + setTable(loadedTable) + }) } - }, [isExpanded, filterText, tableRef, table]); + }, [isExpanded, filterText, tableRef, table]) // Check visibility using the passed down helper if (!isVisible(tablePath)) { - return null; + return null } const handleGenerateQuery = (e: React.MouseEvent) => { - e.stopPropagation(); // Prevent toggling expansion + e.stopPropagation() // Prevent toggling expansion if (onGenerateQuery) { - onGenerateQuery('SELECT', tableRef); + onGenerateQuery('SELECT', tableRef) } - }; + } return (
-
onToggle(tablePath)} - > +
onToggle(tablePath)}> {table.getName()} table - - {isExpanded ? '▼' : '▶'} - - + {isExpanded ? '▼' : '▶'} + {onGenerateQuery && ( -
@@ -99,17 +88,18 @@ const CatalogViewerTable: React.FC = ({
{table.getError()}
) : table.getColumns().length === 0 && isExpanded && table.isLoading() ? (
Loading columns...
- ) : table.getColumns().length > 0 && ( - table.getColumns().map(column => { + ) : ( + table.getColumns().length > 0 && + table.getColumns().map((column) => { const columnPath = buildPath.column( tableRef.catalogName, tableRef.schemaName, tableRef.tableName, column.getName() - ); - + ) + if (!isVisible(columnPath)) { - return null; + return null } return ( @@ -121,13 +111,13 @@ const CatalogViewerTable: React.FC = ({ isVisible={isVisible} onToggle={onToggle} /> - ); + ) }) )}
)}
- ); -}; + ) +} -export default CatalogViewerTable; \ No newline at end of file +export default CatalogViewerTable diff --git a/precise/src/controls/catalog_viewer/ViewerState.ts b/precise/src/controls/catalog_viewer/ViewerState.ts index 8fff867..c02737b 100644 --- a/precise/src/controls/catalog_viewer/ViewerState.ts +++ b/precise/src/controls/catalog_viewer/ViewerState.ts @@ -1,124 +1,107 @@ -import Catalog from './../../schema/Catalog'; -import TableReference from '../../schema/TableReference'; -import SchemaProvider from '../../sql/SchemaProvider'; -import Table from '../../schema/Table'; +import Catalog from './../../schema/Catalog' +import TableReference from '../../schema/TableReference' +import SchemaProvider from '../../sql/SchemaProvider' +import Table from '../../schema/Table' export interface ViewerStateUpdate { - expandedNodes: Set; - matches: Set; + expandedNodes: Set + matches: Set } -export type StateUpdateCallback = (update: ViewerStateUpdate) => void; +export type StateUpdateCallback = (update: ViewerStateUpdate) => void export const buildPath = { catalog: (catalogName: string) => catalogName, - schema: (catalogName: string, schemaName: string) => - `${catalogName}.${schemaName}`, - table: (catalogName: string, schemaName: string, tableName: string) => - `${catalogName}.${schemaName}.${tableName}`, + schema: (catalogName: string, schemaName: string) => `${catalogName}.${schemaName}`, + table: (catalogName: string, schemaName: string, tableName: string) => `${catalogName}.${schemaName}.${tableName}`, column: (catalogName: string, schemaName: string, tableName: string, columnName: string) => - `${catalogName}.${schemaName}.${tableName}.${columnName}` -}; + `${catalogName}.${schemaName}.${tableName}.${columnName}`, +} export class ViewerStateManager { - private userExpanded = new Set(); - private matches = new Set(); - private onStateUpdate: StateUpdateCallback; - private isSearching = false; - private onLoadingChange: (loading: boolean) => void; + private userExpanded = new Set() + private matches = new Set() + private onStateUpdate: StateUpdateCallback + private isSearching = false + private onLoadingChange: (loading: boolean) => void constructor(onStateUpdate: StateUpdateCallback, onLoadingChange: (loading: boolean) => void) { - this.onStateUpdate = onStateUpdate; - this.onLoadingChange = onLoadingChange; - } + this.onStateUpdate = onStateUpdate + this.onLoadingChange = onLoadingChange + } public startSearch(filterText: string, includeColumns: boolean, catalogs: Map) { - this.matches.clear(); - this.isSearching = !!filterText; - + this.matches.clear() + this.isSearching = !!filterText + if (!filterText) { - this.notifyStateUpdate(); - return; + this.notifyStateUpdate() + return } - - const filterItem = (name: string) => - name.toLowerCase().includes(filterText.toLowerCase()); - + + const filterItem = (name: string) => name.toLowerCase().includes(filterText.toLowerCase()) + // Queue tables that need column loading - const tablesToLoad: TableReference[] = []; - + const tablesToLoad: TableReference[] = [] + // Search through catalogs catalogs.forEach((catalog, catalogName) => { if (filterItem(catalogName)) { - this.matches.add(buildPath.catalog(catalogName)); + this.matches.add(buildPath.catalog(catalogName)) } // Search through schemas - catalog.getSchemas().forEach(schema => { - const schemaPath = buildPath.schema(catalogName, schema.getName()); + catalog.getSchemas().forEach((schema) => { + const schemaPath = buildPath.schema(catalogName, schema.getName()) if (filterItem(schema.getName())) { - this.matches.add(schemaPath); + this.matches.add(schemaPath) } // Search through tables - schema.getTables().forEach(table => { - const tablePath = buildPath.table( - catalogName, - schema.getName(), - table.getName() - ); + schema.getTables().forEach((table) => { + const tablePath = buildPath.table(catalogName, schema.getName(), table.getName()) if (filterItem(table.getName())) { - this.matches.add(tablePath); + this.matches.add(tablePath) } // Handle column searching if (includeColumns) { if (table.getColumns().length === 0) { - tablesToLoad.push(new TableReference( - catalogName, - schema.getName(), - table.getName() - )); + tablesToLoad.push(new TableReference(catalogName, schema.getName(), table.getName())) } else { - this.searchTableColumns( - table, - catalogName, - schema.getName(), - table.getName(), - filterItem - ); + this.searchTableColumns(table, catalogName, schema.getName(), table.getName(), filterItem) } } - }); - }); - }); + }) + }) + }) // Process any tables that need column loading if (tablesToLoad.length > 0) { - this.loadTablesSequentially(tablesToLoad, filterItem); + this.loadTablesSequentially(tablesToLoad, filterItem) } - - this.notifyStateUpdate(); + + this.notifyStateUpdate() } private loadTablesSequentially(tablesToLoad: TableReference[], filterItem: (name: string) => boolean) { if (tablesToLoad.length === 0) { - this.onLoadingChange(false); - return; + this.onLoadingChange(false) + return } - this.onLoadingChange(true); - + this.onLoadingChange(true) + const loadNextTable = (index: number) => { if (index >= tablesToLoad.length) { - this.onLoadingChange(false); - return; + this.onLoadingChange(false) + return } - - if (index >= tablesToLoad.length) return; - - const tableRef = tablesToLoad[index]; + + if (index >= tablesToLoad.length) return + + const tableRef = tablesToLoad[index] SchemaProvider.getTableWithCache(tableRef, (loadedTable: Table) => { this.searchTableColumns( loadedTable, @@ -126,16 +109,16 @@ export class ViewerStateManager { tableRef.schemaName, tableRef.tableName, filterItem - ); - this.notifyStateUpdate(); - + ) + this.notifyStateUpdate() + // Load the next table only after this one is complete - loadNextTable(index + 1); - }); - }; - + loadNextTable(index + 1) + }) + } + // Start with the first table - loadNextTable(0); + loadNextTable(0) } private searchTableColumns( @@ -145,61 +128,54 @@ export class ViewerStateManager { tableName: string, filterItem: (name: string) => boolean ) { - table.getColumns().forEach(column => { + table.getColumns().forEach((column) => { if (filterItem(column.getName())) { - const columnPath = buildPath.column( - catalogName, - schemaName, - tableName, - column.getName() - ); - this.matches.add(columnPath); + const columnPath = buildPath.column(catalogName, schemaName, tableName, column.getName()) + this.matches.add(columnPath) } - }); + }) } public toggleExpanded(path: string) { if (this.userExpanded.has(path)) { - this.userExpanded.delete(path); + this.userExpanded.delete(path) } else { - this.userExpanded.add(path); + this.userExpanded.add(path) } - this.notifyStateUpdate(); + this.notifyStateUpdate() } public isVisible(path: string): boolean { if (!this.isSearching) { // In manual mode, must have all parents expanded - const segments = path.split('.'); + const segments = path.split('.') for (let i = 1; i < segments.length; i++) { - const parentPath = segments.slice(0, i).join('.'); + const parentPath = segments.slice(0, i).join('.') if (!this.userExpanded.has(parentPath)) { - return false; + return false } } - return true; + return true } - - return this.matches.has(path) || this.hasMatchingChildren(path); + + return this.matches.has(path) || this.hasMatchingChildren(path) } public isExpanded(path: string): boolean { - return this.userExpanded.has(path); + return this.userExpanded.has(path) } public hasMatchingChildren(path: string): boolean { - if (!this.isSearching) return false; - - const prefix = path + '.'; - return Array.from(this.matches).some(match => - match.startsWith(prefix) - ); + if (!this.isSearching) return false + + const prefix = path + '.' + return Array.from(this.matches).some((match) => match.startsWith(prefix)) } private notifyStateUpdate() { this.onStateUpdate({ expandedNodes: new Set(this.userExpanded), - matches: this.matches - }); + matches: this.matches, + }) } -} \ No newline at end of file +} diff --git a/precise/src/controls/tabs/EnterpriseTabs.tsx b/precise/src/controls/tabs/EnterpriseTabs.tsx index d9a3d35..823cf49 100644 --- a/precise/src/controls/tabs/EnterpriseTabs.tsx +++ b/precise/src/controls/tabs/EnterpriseTabs.tsx @@ -1,159 +1,161 @@ -import React, { Component } from 'react'; -import { DndProvider } from 'react-dnd'; -import { HTML5Backend } from 'react-dnd-html5-backend'; -import './tabs.css'; -import TabItem from './TabItem'; -import TabsEllipsesMenu from './TabsEllipsesMenu'; +import React, { Component } from 'react' +import { DndProvider } from 'react-dnd' +import { HTML5Backend } from 'react-dnd-html5-backend' +import './tabs.css' +import TabItem from './TabItem' +import TabsEllipsesMenu from './TabsEllipsesMenu' import Tabs from './Tabs' -import TabInfo from './TabInfo'; +import TabInfo from './TabInfo' interface EnterpriseTabsProps { - tabs: Tabs; - newTabLabel?: string; - onTabChange?: (tabId: string) => void; - onTabCreate?: () => void; - onTabClose?: (tabId: string) => void; - onTabRename?: (tabId: string, newTitle: string) => void; - onTabPin?: (tabId: string, isPinned: boolean) => void; - onTabsReorder?: (newOrder: string[]) => void; - onTabSelectAndPromote?: (tabId: string) => void; + tabs: Tabs + newTabLabel?: string + onTabChange?: (tabId: string) => void + onTabCreate?: () => void + onTabClose?: (tabId: string) => void + onTabRename?: (tabId: string, newTitle: string) => void + onTabPin?: (tabId: string, isPinned: boolean) => void + onTabsReorder?: (newOrder: string[]) => void + onTabSelectAndPromote?: (tabId: string) => void } interface EnterpriseTabsState { - tabs: T[]; - currentTabId: string; + tabs: T[] + currentTabId: string } class EnterpriseTabs extends Component, EnterpriseTabsState> { - private isUpdating: boolean = false; - - constructor(props: EnterpriseTabsProps) { - super(props); - this.state = { - tabs: props.tabs.getTabs(), - currentTabId: props.tabs.getCurrentTab().id, - }; - } - - componentDidMount() { - this.props.tabs.addChangeListener(this.handleTabsChange); - } - - componentWillUnmount() { - this.props.tabs.removeChangeListener(this.handleTabsChange); - } - - handleTabsChange = () => { - if (this.isUpdating) return; - this.isUpdating = true; - - const newTabs = this.props.tabs.getTabs(); - const newCurrentTab = this.props.tabs.getCurrentTab(); - - this.setState({ - tabs: newTabs, - currentTabId: newCurrentTab.id, - }, () => { - if (newCurrentTab.id !== this.state.currentTabId && this.props.onTabChange) { - this.props.onTabChange(newCurrentTab.id); - } - this.isUpdating = false; - }); - } - - handleTabClick = (tabId: string) => { - if (tabId === this.state.currentTabId) return; - this.props.tabs.setCurrentTab(tabId); - } - - handleTabClose = (tabId: string) => { - this.props.tabs.deleteTab(tabId); - if (this.props.onTabClose) { - this.props.onTabClose(tabId); + private isUpdating: boolean = false + + constructor(props: EnterpriseTabsProps) { + super(props) + this.state = { + tabs: props.tabs.getTabs(), + currentTabId: props.tabs.getCurrentTab().id, + } + } + + componentDidMount() { + this.props.tabs.addChangeListener(this.handleTabsChange) + } + + componentWillUnmount() { + this.props.tabs.removeChangeListener(this.handleTabsChange) + } + + handleTabsChange = () => { + if (this.isUpdating) return + this.isUpdating = true + + const newTabs = this.props.tabs.getTabs() + const newCurrentTab = this.props.tabs.getCurrentTab() + + this.setState( + { + tabs: newTabs, + currentTabId: newCurrentTab.id, + }, + () => { + if (newCurrentTab.id !== this.state.currentTabId && this.props.onTabChange) { + this.props.onTabChange(newCurrentTab.id) + } + this.isUpdating = false + } + ) + } + + handleTabClick = (tabId: string) => { + if (tabId === this.state.currentTabId) return + this.props.tabs.setCurrentTab(tabId) } - } - -handleNewTab = () => { - // Because tabs are created through the modifying the list of tabs, the list is managed by the containing component - if (this.props.onTabCreate) { - // Tabs should be created through - this.props.onTabCreate(); - } else { - // If no handler is provided, directly add the tab - const newTab = this.props.tabs.addTab(); - this.props.tabs.setCurrentTab(newTab.id); - } -} - handleTabRename = (tabId: string, newTitle: string) => { - this.props.tabs.updateTab(tabId, { title: newTitle } as Partial); - if (this.props.onTabRename) { - this.props.onTabRename(tabId, newTitle); + handleTabClose = (tabId: string) => { + this.props.tabs.deleteTab(tabId) + if (this.props.onTabClose) { + this.props.onTabClose(tabId) + } } - } - - handleTabPin = (tabId: string) => { - const tab = this.props.tabs.getTabs().find(t => t.id === tabId); - if (tab) { - const newPinnedState = !tab.isPinned; - this.props.tabs.pinTab(tabId, newPinnedState); - if (this.props.onTabPin) { - this.props.onTabPin(tabId, newPinnedState); - } + + handleNewTab = () => { + // Because tabs are created through the modifying the list of tabs, the list is managed by the containing component + if (this.props.onTabCreate) { + // Tabs should be created through + this.props.onTabCreate() + } else { + // If no handler is provided, directly add the tab + const newTab = this.props.tabs.addTab() + this.props.tabs.setCurrentTab(newTab.id) + } } - } - - handleTabSelectAndPromote = (tabId: string) => { - if (tabId === this.state.currentTabId) return; - this.props.tabs.moveToFront(tabId); - this.props.tabs.setCurrentTab(tabId); - if (this.props.onTabSelectAndPromote) { - this.props.onTabSelectAndPromote(tabId); + + handleTabRename = (tabId: string, newTitle: string) => { + this.props.tabs.updateTab(tabId, { title: newTitle } as Partial) + if (this.props.onTabRename) { + this.props.onTabRename(tabId, newTitle) + } } - } - - moveTab = (fromIndex: number, toIndex: number) => { - const tabs = [...this.state.tabs]; - const [movedTab] = tabs.splice(fromIndex, 1); - tabs.splice(toIndex, 0, movedTab); - const newOrder = tabs.map(t => t.id); - this.props.tabs.updateTabOrder(newOrder); - if (this.props.onTabsReorder) { - this.props.onTabsReorder(newOrder); + + handleTabPin = (tabId: string) => { + const tab = this.props.tabs.getTabs().find((t) => t.id === tabId) + if (tab) { + const newPinnedState = !tab.isPinned + this.props.tabs.pinTab(tabId, newPinnedState) + if (this.props.onTabPin) { + this.props.onTabPin(tabId, newPinnedState) + } + } + } + + handleTabSelectAndPromote = (tabId: string) => { + if (tabId === this.state.currentTabId) return + this.props.tabs.moveToFront(tabId) + this.props.tabs.setCurrentTab(tabId) + if (this.props.onTabSelectAndPromote) { + this.props.onTabSelectAndPromote(tabId) + } + } + + moveTab = (fromIndex: number, toIndex: number) => { + const tabs = [...this.state.tabs] + const [movedTab] = tabs.splice(fromIndex, 1) + tabs.splice(toIndex, 0, movedTab) + const newOrder = tabs.map((t) => t.id) + this.props.tabs.updateTabOrder(newOrder) + if (this.props.onTabsReorder) { + this.props.onTabsReorder(newOrder) + } + } + + render() { + const { tabs, currentTabId } = this.state + const { newTabLabel = '+' } = this.props + + return ( + +
+
+ {tabs.map((tab, index) => ( + this.handleTabClick(tab.id)} + handleTabClose={() => this.handleTabClose(tab.id)} + handleTabRename={(id, newTitle) => this.handleTabRename(tab.id, newTitle)} + handleTabPin={() => this.handleTabPin(tab.id)} + /> + ))} +
+ {newTabLabel} +
+
+ +
+
+ ) } - } - - render() { - const { tabs, currentTabId } = this.state; - const { newTabLabel = '+' } = this.props; - - return ( - -
-
- {tabs.map((tab, index) => ( - this.handleTabClick(tab.id)} - handleTabClose={() => this.handleTabClose(tab.id)} - handleTabRename={(id, newTitle) => this.handleTabRename(tab.id, newTitle)} - handleTabPin={() => this.handleTabPin(tab.id)} - /> - ))} -
{newTabLabel}
-
- -
-
- ); - } } -export default EnterpriseTabs; \ No newline at end of file +export default EnterpriseTabs diff --git a/precise/src/controls/tabs/TabInfo.tsx b/precise/src/controls/tabs/TabInfo.tsx index 85c86d0..6ddc458 100644 --- a/precise/src/controls/tabs/TabInfo.tsx +++ b/precise/src/controls/tabs/TabInfo.tsx @@ -1,12 +1,11 @@ - // Define a generic TabInfo interface interface TabInfo { - id: string; - title: string; - isPinned: boolean; + id: string + title: string + isPinned: boolean } -export default TabInfo; +export default TabInfo // import { v4 as uuidv4 } from 'uuid'; @@ -22,4 +21,4 @@ export default TabInfo; // } // } -// export default TabInfo; \ No newline at end of file +// export default TabInfo; diff --git a/precise/src/controls/tabs/TabItem.tsx b/precise/src/controls/tabs/TabItem.tsx index effadda..c591cb2 100644 --- a/precise/src/controls/tabs/TabItem.tsx +++ b/precise/src/controls/tabs/TabItem.tsx @@ -1,129 +1,135 @@ -import React, { useState, useRef, useEffect } from 'react'; -import { useDrag, useDrop } from 'react-dnd'; -import TabInfo from './TabInfo'; -import PinUpIcon from '../../assets/pin_up.png'; -import PinDownIcon from '../../assets/pin_down.png'; -import CloseIcon from '../../assets/close.png'; +import React, { useState, useRef, useEffect } from 'react' +import { useDrag, useDrop } from 'react-dnd' +import TabInfo from './TabInfo' +import PinUpIcon from '../../assets/pin_up.png' +import PinDownIcon from '../../assets/pin_down.png' +import CloseIcon from '../../assets/close.png' const ItemType = { - TAB: 'tab', -}; + TAB: 'tab', +} interface TabItemProps { - tab: T; - isActive: boolean; - index: number; - moveTab: (fromIndex: number, toIndex: number) => void; - handleTabClick: (tabId: string) => void; - handleTabClose: (tabId: string) => void; - handleTabRename: (tabId: string, newTitle: string) => void; - handleTabPin: (tabId: string) => void; + tab: T + isActive: boolean + index: number + moveTab: (fromIndex: number, toIndex: number) => void + handleTabClick: (tabId: string) => void + handleTabClose: (tabId: string) => void + handleTabRename: (tabId: string, newTitle: string) => void + handleTabPin: (tabId: string) => void } -function TabItem({ - tab, - isActive, - index, - moveTab, - handleTabClick, - handleTabClose, - handleTabRename, - handleTabPin - }: TabItemProps) { - const [isEditing, setIsEditing] = useState(false); - const [editedTitle, setEditedTitle] = useState(tab.title); - const inputRef = useRef(null); +function TabItem({ + tab, + isActive, + index, + moveTab, + handleTabClick, + handleTabClose, + handleTabRename, + handleTabPin, +}: TabItemProps) { + const [isEditing, setIsEditing] = useState(false) + const [editedTitle, setEditedTitle] = useState(tab.title) + const inputRef = useRef(null) - const [{ isDragging }, drag, preview] = useDrag({ - type: ItemType.TAB, - item: () => ({ index }), - collect: (monitor) => ({ - isDragging: monitor.isDragging(), - }), - canDrag: () => !isEditing, // Disable dragging when editing - }); + const [{ isDragging }, drag, preview] = useDrag({ + type: ItemType.TAB, + item: () => ({ index }), + collect: (monitor) => ({ + isDragging: monitor.isDragging(), + }), + canDrag: () => !isEditing, // Disable dragging when editing + }) - const [, drop] = useDrop({ - accept: ItemType.TAB, - hover: (draggedItem: { index: number }) => { - if (draggedItem.index !== index) { - moveTab(draggedItem.index, index); - draggedItem.index = index; - } - }, - }); + const [, drop] = useDrop({ + accept: ItemType.TAB, + hover: (draggedItem: { index: number }) => { + if (draggedItem.index !== index) { + moveTab(draggedItem.index, index) + draggedItem.index = index + } + }, + }) - useEffect(() => { - if (isEditing && inputRef.current) { - inputRef.current.focus(); - inputRef.current.select(); // Select all text when editing starts - } - }, [isEditing]); + useEffect(() => { + if (isEditing && inputRef.current) { + inputRef.current.focus() + inputRef.current.select() // Select all text when editing starts + } + }, [isEditing]) - const handleDoubleClick = (e: React.MouseEvent) => { - e.stopPropagation(); - setIsEditing(true); - }; + const handleDoubleClick = (e: React.MouseEvent) => { + e.stopPropagation() + setIsEditing(true) + } - const handleInputChange = (event: React.ChangeEvent) => { - setEditedTitle(event.target.value); - }; + const handleInputChange = (event: React.ChangeEvent) => { + setEditedTitle(event.target.value) + } - const handleInputBlur = () => { - setIsEditing(false); - if (editedTitle.trim() !== tab.title && editedTitle.trim() !== '') { - handleTabRename(tab.id, editedTitle.trim()); - } else { - setEditedTitle(tab.title); // Reset to original title if empty or unchanged + const handleInputBlur = () => { + setIsEditing(false) + if (editedTitle.trim() !== tab.title && editedTitle.trim() !== '') { + handleTabRename(tab.id, editedTitle.trim()) + } else { + setEditedTitle(tab.title) // Reset to original title if empty or unchanged + } } - }; - const handleInputKeyPress = (event: React.KeyboardEvent) => { - if (event.key === 'Enter') { - handleInputBlur(); + const handleInputKeyPress = (event: React.KeyboardEvent) => { + if (event.key === 'Enter') { + handleInputBlur() + } } - }; - return ( -
preview(drop(node))} - className={`tab-item ${isActive ? 'tab-item-selected' : ''} ${isDragging ? 'dragging' : ''} ${tab.isPinned ? 'pinned' : ''}`} - onClick={() => handleTabClick(tab.id)} - > -
- {isEditing ? ( - e.stopPropagation()} - style={{ cursor: 'text' }} - /> - ) : ( -
- {tab.title} -
- )} -
-
-
{ e.stopPropagation(); handleTabPin(tab.id); }} - > - Pin -
-
{ e.stopPropagation(); handleTabClose(tab.id); }} + return ( +
preview(drop(node))} + className={`tab-item ${isActive ? 'tab-item-selected' : ''} ${isDragging ? 'dragging' : ''} ${tab.isPinned ? 'pinned' : ''}`} + onClick={() => handleTabClick(tab.id)} > - Close +
+ {isEditing ? ( + e.stopPropagation()} + style={{ cursor: 'text' }} + /> + ) : ( +
+ {tab.title} +
+ )} +
+
+
{ + e.stopPropagation() + handleTabPin(tab.id) + }} + > + Pin +
+
{ + e.stopPropagation() + handleTabClose(tab.id) + }} + > + Close +
+
-
-
- ); + ) } -export default TabItem; \ No newline at end of file +export default TabItem diff --git a/precise/src/controls/tabs/Tabs.tsx b/precise/src/controls/tabs/Tabs.tsx index ae86be2..6119983 100644 --- a/precise/src/controls/tabs/Tabs.tsx +++ b/precise/src/controls/tabs/Tabs.tsx @@ -1,144 +1,143 @@ -import TabInfo from './TabInfo'; +import TabInfo from './TabInfo' // Abstract base class for tab management abstract class Tabs { - protected tabs: T[]; - protected currentTabId: string; - protected changeListeners: (() => void)[] = []; + protected tabs: T[] + protected currentTabId: string + protected changeListeners: (() => void)[] = [] constructor() { - this.tabs = this.loadTabs(); + this.tabs = this.loadTabs() if (this.tabs.length === 0) { - this.tabs.push(this.createNewTab()); + this.tabs.push(this.createNewTab()) } - this.currentTabId = this.tabs[0].id; + this.currentTabId = this.tabs[0].id } - abstract loadTabs(): T[]; - abstract saveTabs(): void; - abstract deleteTabFromStorage(tabId: string): void; - abstract createNewTab(): T; + abstract loadTabs(): T[] + abstract saveTabs(): void + abstract deleteTabFromStorage(tabId: string): void + abstract createNewTab(): T getTabs(): T[] { - return [...this.tabs]; // Return a shallow copy + return [...this.tabs] // Return a shallow copy } getCurrentTab(): T { - if (!this.currentTabId || !this.tabs.some(t => t.id === this.currentTabId)) { + if (!this.currentTabId || !this.tabs.some((t) => t.id === this.currentTabId)) { if (this.tabs.length === 0) { - this.tabs.push(this.createNewTab()); + this.tabs.push(this.createNewTab()) } - this.currentTabId = this.tabs[0].id; + this.currentTabId = this.tabs[0].id } - return this.tabs.find(t => t.id === this.currentTabId)!; + return this.tabs.find((t) => t.id === this.currentTabId)! } setCurrentTab(tabId: string): void { - const tab = this.tabs.find(t => t.id === tabId); + const tab = this.tabs.find((t) => t.id === tabId) if (tab) { - this.currentTabId = tabId; + this.currentTabId = tabId } else { - this.currentTabId = this.tabs[0]?.id || this.createNewTab().id; + this.currentTabId = this.tabs[0]?.id || this.createNewTab().id } - this.notifyListeners(); + this.notifyListeners() } - addTab(front : boolean = false, title: string = 'New Tab'): T { - const newTab = this.createNewTab(); - this.tabs.push(newTab); - this.currentTabId = newTab.id; + addTab(front: boolean = false, title: string = 'New Tab'): T { + const newTab = this.createNewTab() + this.tabs.push(newTab) + this.currentTabId = newTab.id if (front) { - this.moveToFrontAfterPinned(newTab.id); - } - else { - this.saveTabs(); - this.notifyListeners(); + this.moveToFrontAfterPinned(newTab.id) + } else { + this.saveTabs() + this.notifyListeners() } - return newTab; + return newTab } deleteTab(tabId: string): void { - this.tabs = this.tabs.filter(t => t.id !== tabId); + this.tabs = this.tabs.filter((t) => t.id !== tabId) if (this.tabs.length === 0) { - this.addTab(); + this.addTab() } else if (this.currentTabId === tabId) { - this.currentTabId = this.tabs[0].id; + this.currentTabId = this.tabs[0].id } - this.deleteTabFromStorage(tabId); - this.notifyListeners(); + this.deleteTabFromStorage(tabId) + this.notifyListeners() } updateTab(tabId: string, updates: Partial): void { - const tabIndex = this.tabs.findIndex(t => t.id === tabId); + const tabIndex = this.tabs.findIndex((t) => t.id === tabId) if (tabIndex !== -1) { - this.tabs[tabIndex] = { ...this.tabs[tabIndex], ...updates }; - this.saveTabs(); - this.notifyListeners(); + this.tabs[tabIndex] = { ...this.tabs[tabIndex], ...updates } + this.saveTabs() + this.notifyListeners() } } updateTabOrder(newOrder: string[]): void { - if (newOrder.every(id => this.tabs.some(t => t.id === id)) && newOrder.length === this.tabs.length) { - this.tabs = newOrder.map(id => this.tabs.find(t => t.id === id)!); - this.saveTabs(); - this.notifyListeners(); + if (newOrder.every((id) => this.tabs.some((t) => t.id === id)) && newOrder.length === this.tabs.length) { + this.tabs = newOrder.map((id) => this.tabs.find((t) => t.id === id)!) + this.saveTabs() + this.notifyListeners() } else { - console.error('Invalid tab order update'); + console.error('Invalid tab order update') } } pinTab(tabId: string, isPinned: boolean): void { - const tabIndex = this.tabs.findIndex(t => t.id === tabId); + const tabIndex = this.tabs.findIndex((t) => t.id === tabId) if (tabIndex !== -1) { - this.tabs[tabIndex].isPinned = isPinned; - this.sortTabs(); - this.saveTabs(); - this.notifyListeners(); + this.tabs[tabIndex].isPinned = isPinned + this.sortTabs() + this.saveTabs() + this.notifyListeners() } } moveToFront(tabId: string): void { - const index = this.tabs.findIndex(t => t.id === tabId); + const index = this.tabs.findIndex((t) => t.id === tabId) if (index > -1) { - const [tab] = this.tabs.splice(index, 1); - this.tabs.unshift(tab); - this.setCurrentTab(tabId); - this.saveTabs(); - this.notifyListeners(); + const [tab] = this.tabs.splice(index, 1) + this.tabs.unshift(tab) + this.setCurrentTab(tabId) + this.saveTabs() + this.notifyListeners() } } moveToFrontAfterPinned(tabId: string): void { - const index = this.tabs.findIndex(t => t.id === tabId); + const index = this.tabs.findIndex((t) => t.id === tabId) if (index > -1) { - const pinnedTabs = this.tabs.filter(t => t.isPinned); - const pinnedCount = pinnedTabs.length; - const [tab] = this.tabs.splice(index, 1); - this.tabs.splice(pinnedCount, 0, tab); - this.setCurrentTab(tabId); - this.saveTabs(); - this.notifyListeners(); + const pinnedTabs = this.tabs.filter((t) => t.isPinned) + const pinnedCount = pinnedTabs.length + const [tab] = this.tabs.splice(index, 1) + this.tabs.splice(pinnedCount, 0, tab) + this.setCurrentTab(tabId) + this.saveTabs() + this.notifyListeners() } } protected sortTabs(): void { this.tabs.sort((a, b) => { - if (a.isPinned === b.isPinned) return 0; - return a.isPinned ? -1 : 1; - }); + if (a.isPinned === b.isPinned) return 0 + return a.isPinned ? -1 : 1 + }) } addChangeListener(listener: () => void) { - this.changeListeners.push(listener); + this.changeListeners.push(listener) } removeChangeListener(listener: () => void) { - this.changeListeners = this.changeListeners.filter(l => l !== listener); + this.changeListeners = this.changeListeners.filter((l) => l !== listener) } protected notifyListeners() { - this.changeListeners.forEach(listener => listener()); + this.changeListeners.forEach((listener) => listener()) } } -export default Tabs; \ No newline at end of file +export default Tabs diff --git a/precise/src/controls/tabs/TabsEllipsesMenu.tsx b/precise/src/controls/tabs/TabsEllipsesMenu.tsx index 3b8f9b6..2e729bb 100644 --- a/precise/src/controls/tabs/TabsEllipsesMenu.tsx +++ b/precise/src/controls/tabs/TabsEllipsesMenu.tsx @@ -1,76 +1,74 @@ -import React, { useState, useRef, useEffect } from 'react'; -import TabInfo from './TabInfo'; +import React, { useState, useRef, useEffect } from 'react' +import TabInfo from './TabInfo' interface TabsEllipsesMenuProps { - tabs: T[]; - onTabSelect: (tabId: string) => void; - filterPlaceholder?: string; + tabs: T[] + onTabSelect: (tabId: string) => void + filterPlaceholder?: string } -function TabsEllipsesMenu({ - tabs, - onTabSelect, - filterPlaceholder = "Filter tabs..." +function TabsEllipsesMenu({ + tabs, + onTabSelect, + filterPlaceholder = 'Filter tabs...', }: TabsEllipsesMenuProps) { - const [isOpen, setIsOpen] = useState(false); - const [filter, setFilter] = useState(''); - const menuRef = useRef(null); + const [isOpen, setIsOpen] = useState(false) + const [filter, setFilter] = useState('') + const menuRef = useRef(null) - const filteredTabs = tabs.filter(tab => - tab.title.toLowerCase().includes(filter.toLowerCase()) - ); + const filteredTabs = tabs.filter((tab) => tab.title.toLowerCase().includes(filter.toLowerCase())) - useEffect(() => { - function handleClickOutside(event: MouseEvent) { - if (menuRef.current && !menuRef.current.contains(event.target as Node)) { - setIsOpen(false); - } - } + useEffect(() => { + function handleClickOutside(event: MouseEvent) { + if (menuRef.current && !menuRef.current.contains(event.target as Node)) { + setIsOpen(false) + } + } - document.addEventListener("mousedown", handleClickOutside); - return () => { - document.removeEventListener("mousedown", handleClickOutside); - }; - }, [menuRef]); + document.addEventListener('mousedown', handleClickOutside) + return () => { + document.removeEventListener('mousedown', handleClickOutside) + } + }, [menuRef]) - const handleEllipsesClick = (event: React.MouseEvent) => { - event.stopPropagation(); - setIsOpen(true); - }; + const handleEllipsesClick = (event: React.MouseEvent) => { + event.stopPropagation() + setIsOpen(true) + } - return ( -
- - {isOpen && ( -
- setFilter(e.target.value)} - autoFocus - /> -
- {filteredTabs.map(tab => ( -
{ - onTabSelect(tab.id); - setIsOpen(false); - }} - > - {tab.title} - {tab.isPinned && 📌} -
- ))} -
+ return ( +
+ + {isOpen && ( +
+ setFilter(e.target.value)} + autoFocus + /> +
+ {filteredTabs.map((tab) => ( +
{ + onTabSelect(tab.id) + setIsOpen(false) + }} + > + {tab.title} + {tab.isPinned && 📌} +
+ ))} +
+
+ )}
- )} -
- ); + ) } -export default TabsEllipsesMenu; \ No newline at end of file +export default TabsEllipsesMenu diff --git a/precise/src/main.tsx b/precise/src/main.tsx index a94b633..b5c4551 100644 --- a/precise/src/main.tsx +++ b/precise/src/main.tsx @@ -9,4 +9,4 @@ import './style/query-editor.css' import './style/results.css' import './style/control.css' -ReactDOM.createRoot(document.getElementById('root')!).render(); \ No newline at end of file +ReactDOM.createRoot(document.getElementById('root')!).render() diff --git a/precise/src/schema/Catalog.ts b/precise/src/schema/Catalog.ts index cdef919..ddd61c9 100644 --- a/precise/src/schema/Catalog.ts +++ b/precise/src/schema/Catalog.ts @@ -1,47 +1,46 @@ -import Schema from "./Schema"; +import Schema from './Schema' class Catalog { - private name: string; - private type: string; - private errorMessage: string = ""; - private schemas : Map = new Map(); + private name: string + private type: string + private errorMessage: string = '' + private schemas: Map = new Map() constructor(name: string, type: string) { - this.name = name; - this.type = type; - + this.name = name + this.type = type } - getName() : string { - return this.name; + getName(): string { + return this.name } - getType() : string { - return this.type; + getType(): string { + return this.type } - getOrAdd(schema : Schema) : Schema { + getOrAdd(schema: Schema): Schema { if (!this.schemas.has(schema.getName())) { - this.schemas.set(schema.getName(), schema); + this.schemas.set(schema.getName(), schema) } - return this.schemas.get(schema.getName()) as Schema; + return this.schemas.get(schema.getName()) as Schema } - getSchemas() : Map { - return this.schemas; + getSchemas(): Map { + return this.schemas } setErrorMessage(error: string) { - this.errorMessage = error; + this.errorMessage = error } clearErrorMessage() { - this.errorMessage = ""; + this.errorMessage = '' } getError() { - return this.errorMessage; + return this.errorMessage } } -export default Catalog; \ No newline at end of file +export default Catalog diff --git a/precise/src/schema/Column.ts b/precise/src/schema/Column.ts index 9e0217d..c1346b8 100644 --- a/precise/src/schema/Column.ts +++ b/precise/src/schema/Column.ts @@ -1,50 +1,52 @@ -import TrinoQueryRunner from "../AsyncTrinoClient"; +import TrinoQueryRunner from '../AsyncTrinoClient' class Column { - private name: string; - private type : string; - private extra : string; - private comment : string; - private sampleValues : string[] = []; + private name: string + private type: string + private extra: string + private comment: string + private sampleValues: string[] = [] constructor(name: string, type: string, extra: string, comment: string) { - this.name = name; - this.type = type; - this.extra = extra; - this.comment = comment; - this.sampleValues = []; + this.name = name + this.type = type + this.extra = extra + this.comment = comment + this.sampleValues = [] } getName() { - return this.name; + return this.name } getType() { - return this.type; + return this.type } getExtra() { - return this.extra; + return this.extra } getComment() { - return this.comment; + return this.comment } getExtraOrComment() { - return this.extra ? this.extra : this.comment; + return this.extra ? this.extra : this.comment } getSampleValues(TableRef: any, callback: any) { // get sample values for this column - new TrinoQueryRunner().SetAllResultsCallback((results: any[]) => { - this.sampleValues = []; - for (let i = 0; i < results.length; i++) { - this.sampleValues.push(results[i][0]); - } - callback(this.sampleValues); - }).StartQuery("SELECT " + this.name + " FROM " + TableRef.fullyQualified + " LIMIT 5"); + new TrinoQueryRunner() + .SetAllResultsCallback((results: any[]) => { + this.sampleValues = [] + for (let i = 0; i < results.length; i++) { + this.sampleValues.push(results[i][0]) + } + callback(this.sampleValues) + }) + .StartQuery('SELECT ' + this.name + ' FROM ' + TableRef.fullyQualified + ' LIMIT 5') } } -export default Column; \ No newline at end of file +export default Column diff --git a/precise/src/schema/Queries.ts b/precise/src/schema/Queries.ts index bf3ead9..709670d 100644 --- a/precise/src/schema/Queries.ts +++ b/precise/src/schema/Queries.ts @@ -1,100 +1,102 @@ -import QueryInfo from './QueryInfo'; -import QueryType from './QueryType'; -import { v4 as uuidv4 } from 'uuid'; -import Tabs from './../controls/tabs/Tabs'; +import QueryInfo from './QueryInfo' +import QueryType from './QueryType' +import { v4 as uuidv4 } from 'uuid' +import Tabs from './../controls/tabs/Tabs' class Queries extends Tabs { loadTabs(): QueryInfo[] { - const queryList: QueryInfo[] = []; + const queryList: QueryInfo[] = [] for (let i = 0; i < localStorage.length; i++) { - const key = localStorage.key(i); + const key = localStorage.key(i) if (key && key.startsWith('query_')) { - const value = localStorage.getItem(key); + const value = localStorage.getItem(key) if (value) { try { - const queryInfo = JSON.parse(value); - queryList.push(new QueryInfo( - queryInfo.title, - queryInfo.type, - queryInfo.query, - queryInfo.id, - queryInfo.isPinned, - queryInfo.catalog, - queryInfo.schema - )); + const queryInfo = JSON.parse(value) + queryList.push( + new QueryInfo( + queryInfo.title, + queryInfo.type, + queryInfo.query, + queryInfo.id, + queryInfo.isPinned, + queryInfo.catalog, + queryInfo.schema + ) + ) } catch (e) { - console.error('Error parsing stored query:', e); - localStorage.removeItem(key); + console.error('Error parsing stored query:', e) + localStorage.removeItem(key) } } } } // Handle query from URL - const urlParams = new URLSearchParams(window.location.search); - const query = urlParams.get('q'); - const title = urlParams.get('n') ?? 'Imported Query'; + const urlParams = new URLSearchParams(window.location.search) + const query = urlParams.get('q') + const title = urlParams.get('n') ?? 'Imported Query' if (query) { - const newQueryId = uuidv4(); - const newQuery = new QueryInfo(title, QueryType.FROM_QUERY_STRING, query, newQueryId, false); - queryList.push(newQuery); - this.saveTab(newQuery); + const newQueryId = uuidv4() + const newQuery = new QueryInfo(title, QueryType.FROM_QUERY_STRING, query, newQueryId, false) + queryList.push(newQuery) + this.saveTab(newQuery) } - return queryList; + return queryList } saveTabs(): void { - this.tabs.forEach(query => this.saveTab(query)); + this.tabs.forEach((query) => this.saveTab(query)) } private saveTab(query: QueryInfo): void { - localStorage.setItem(`query_${query.id}`, JSON.stringify(query)); + localStorage.setItem(`query_${query.id}`, JSON.stringify(query)) } deleteTabFromStorage(tabId: string): void { - localStorage.removeItem(`query_${tabId}`); + localStorage.removeItem(`query_${tabId}`) } createNewTab(): QueryInfo { - return new QueryInfo('New Query', QueryType.USER_ADDED, '', uuidv4(), false); + return new QueryInfo('New Query', QueryType.USER_ADDED, '', uuidv4(), false) } // Query-specific methods getCurrentQuery(): QueryInfo { - return this.getCurrentTab(); + return this.getCurrentTab() } - addQuery(front : boolean = false, title: string = 'New Query'): QueryInfo { - return this.addTab(front, title); + addQuery(front: boolean = false, title: string = 'New Query'): QueryInfo { + return this.addTab(front, title) } updateQuery(queryId: string, updates: Partial): void { - this.updateTab(queryId, updates); + this.updateTab(queryId, updates) } deleteQuery(queryId: string): void { - this.deleteTab(queryId); + this.deleteTab(queryId) } setCurrentQuery(queryId: string): void { - this.setCurrentTab(queryId); + this.setCurrentTab(queryId) } updateQueryOrder(newOrder: string[]): void { - this.updateTabOrder(newOrder); + this.updateTabOrder(newOrder) } moveQueryToFront(queryId: string): void { - this.moveToFront(queryId); + this.moveToFront(queryId) } // Additional query-specific method getQueryContent(queryId: string): string { - const query = this.tabs.find(q => q.id === queryId); - return query ? query.query : ''; + const query = this.tabs.find((q) => q.id === queryId) + return query ? query.query : '' } } -export default Queries; \ No newline at end of file +export default Queries diff --git a/precise/src/schema/QueryInfo.tsx b/precise/src/schema/QueryInfo.tsx index 8b700d7..8992b79 100644 --- a/precise/src/schema/QueryInfo.tsx +++ b/precise/src/schema/QueryInfo.tsx @@ -1,16 +1,16 @@ -import TabInfo from './../controls/tabs/TabInfo'; -import QueryType from './QueryType'; +import TabInfo from './../controls/tabs/TabInfo' +import QueryType from './QueryType' class QueryInfo implements TabInfo { - constructor( - public title: string, - public type: QueryType, - public query: string, - public id: string, - public isPinned: boolean, - public catalog?: string, - public schema?: string, - ) {} + constructor( + public title: string, + public type: QueryType, + public query: string, + public id: string, + public isPinned: boolean, + public catalog?: string, + public schema?: string + ) {} } -export default QueryInfo; \ No newline at end of file +export default QueryInfo diff --git a/precise/src/schema/QueryType.tsx b/precise/src/schema/QueryType.tsx index b5a200b..d0b7c8d 100644 --- a/precise/src/schema/QueryType.tsx +++ b/precise/src/schema/QueryType.tsx @@ -1,8 +1,8 @@ -export enum QueryType { +enum QueryType { USER_ADDED = 'USER_ADDED', SAMPLE_QUERY = 'SAMPLE_QUERY', RESTORED_FROM_LOCAL_STORAGE = 'RESTORED_FROM_LOCAL_STORAGE', - FROM_QUERY_STRING = 'FROM_QUERY_STRING' + FROM_QUERY_STRING = 'FROM_QUERY_STRING', } -export default QueryType; \ No newline at end of file +export default QueryType diff --git a/precise/src/schema/Schema.ts b/precise/src/schema/Schema.ts index b29ddae..fe49996 100644 --- a/precise/src/schema/Schema.ts +++ b/precise/src/schema/Schema.ts @@ -1,24 +1,24 @@ -import Table from './Table'; +import Table from './Table' class Schema { - private name: string; - private tables : Map = new Map(); + private name: string + private tables: Map = new Map() constructor(name: string) { - this.name = name; + this.name = name } getName() { - return this.name; + return this.name } getTables() { - return this.tables; + return this.tables } - addTable(table : Table) { - this.tables.set(table.getName(), table); + addTable(table: Table) { + this.tables.set(table.getName(), table) } } -export default Schema; \ No newline at end of file +export default Schema diff --git a/precise/src/schema/Table.ts b/precise/src/schema/Table.ts index 5237a89..aca3ee3 100644 --- a/precise/src/schema/Table.ts +++ b/precise/src/schema/Table.ts @@ -1,55 +1,55 @@ -import Column from './Column'; +import Column from './Column' class Table { - private name: string; - private columns: Column[] = []; - private error: string = ""; - private isLoadingColumns: boolean = false; + private name: string + private columns: Column[] = [] + private error: string = '' + private isLoadingColumns: boolean = false constructor(name: string) { - this.name = name; + this.name = name } getName() { - return this.name; + return this.name } getColumns() { - return this.columns; + return this.columns } getError() { - return this.error; + return this.error } isLoading() { - return this.isLoadingColumns; + return this.isLoadingColumns } setLoading(loading: boolean) { - this.isLoadingColumns = loading; + this.isLoadingColumns = loading } hasLoadedColumns() { - return this.columns.length > 0 || this.error !== ""; + return this.columns.length > 0 || this.error !== '' } getColumnsForSelect() { - return this.columns.map(column => column.getName()).join(", "); + return this.columns.map((column) => column.getName()).join(', ') } getFullSchemaAsString() { - var fullSchema = ""; - this.columns.forEach(column => { - fullSchema += column.getName() + " " + column.getType() + "
"; - }); - return fullSchema; + let fullSchema = '' + this.columns.forEach((column) => { + fullSchema += column.getName() + ' ' + column.getType() + '
' + }) + return fullSchema } setError(error: string) { - this.error = error; - this.isLoadingColumns = false; + this.error = error + this.isLoadingColumns = false } } -export default Table; \ No newline at end of file +export default Table diff --git a/precise/src/schema/TableReference.ts b/precise/src/schema/TableReference.ts index 1dcd8fb..3253e35 100644 --- a/precise/src/schema/TableReference.ts +++ b/precise/src/schema/TableReference.ts @@ -1,47 +1,51 @@ -import Catalog from "./Catalog"; -import Schema from "./Schema"; -import Table from "./Table"; -import SchemaProvider from "./../sql/SchemaProvider"; +import Catalog from './Catalog' +import Schema from './Schema' +import Table from './Table' +import SchemaProvider from './../sql/SchemaProvider' // tables may or may not exist in the catalog so we need to maintain a reference to both names and the actual objects class TableReference { - catalogName : string; - schemaName : string; - tableName : string; - fullyQualified : string; - - constructor(catalogName : string, schemaName : string, tableName : string) - { - this.catalogName = catalogName; - this.schemaName = schemaName; - this.tableName = tableName; - this.fullyQualified = this.getFullyQualified(); + catalogName: string + schemaName: string + tableName: string + fullyQualified: string + + constructor(catalogName: string, schemaName: string, tableName: string) { + this.catalogName = catalogName + this.schemaName = schemaName + this.tableName = tableName + this.fullyQualified = this.getFullyQualified() } - getCatalog() : Catalog | undefined { - return SchemaProvider.catalogs.get(this.catalogName); + getCatalog(): Catalog | undefined { + return SchemaProvider.catalogs.get(this.catalogName) } - getSchema() : Schema | undefined { - return SchemaProvider.catalogs.get(this.catalogName)?.getSchemas().get(this.schemaName); + getSchema(): Schema | undefined { + return SchemaProvider.catalogs.get(this.catalogName)?.getSchemas().get(this.schemaName) } - getTable() : Table | undefined { - return SchemaProvider.catalogs.get(this.catalogName)?.getSchemas().get(this.schemaName)?.getTables().get(this.tableName); + getTable(): Table | undefined { + return SchemaProvider.catalogs + .get(this.catalogName) + ?.getSchemas() + .get(this.schemaName) + ?.getTables() + .get(this.tableName) } - static isFullyQualified(proposedName : string) { - return proposedName.split(".").length === 3; + static isFullyQualified(proposedName: string) { + return proposedName.split('.').length === 3 } - static fromFullyQualified(fullyQualifiedTableName : string) { - const parts = fullyQualifiedTableName.split("."); - return new TableReference(parts[0], parts[1], parts[2]); + static fromFullyQualified(fullyQualifiedTableName: string) { + const parts = fullyQualifiedTableName.split('.') + return new TableReference(parts[0], parts[1], parts[2]) } - private getFullyQualified() : string { - return this.catalogName + "." + this.schemaName + "." + this.tableName; + private getFullyQualified(): string { + return this.catalogName + '.' + this.schemaName + '.' + this.tableName } } -export default TableReference; \ No newline at end of file +export default TableReference diff --git a/precise/src/sql/NamedQuery.ts b/precise/src/sql/NamedQuery.ts index 238f8da..3570602 100644 --- a/precise/src/sql/NamedQuery.ts +++ b/precise/src/sql/NamedQuery.ts @@ -1,12 +1,11 @@ class NamedQuery { - - public name: string; - public node: any; + public name: string + public node: any constructor(name: string, node: any) { - this.name = name; - this.node = node; + this.name = name + this.node = node } } -export default NamedQuery; \ No newline at end of file +export default NamedQuery diff --git a/precise/src/sql/SchemaProvider.ts b/precise/src/sql/SchemaProvider.ts index 8824a71..9e2c41b 100644 --- a/precise/src/sql/SchemaProvider.ts +++ b/precise/src/sql/SchemaProvider.ts @@ -1,163 +1,173 @@ -import TrinoQueryRunner from "../AsyncTrinoClient"; -import Column from "../schema/Column"; -import Catalog from "./../schema/Catalog"; -import Schema from "./../schema/Schema"; -import Table from "./../schema/Table"; -import TableReference from "./../schema/TableReference"; +import TrinoQueryRunner from '../AsyncTrinoClient' +import Column from '../schema/Column' +import Catalog from './../schema/Catalog' +import Schema from './../schema/Schema' +import Table from './../schema/Table' +import TableReference from './../schema/TableReference' class SchemaProvider { - // error message from last catalog fetch so that it can be displayed to the user - public static lastSchemaFetchError : string | undefined = undefined; + public static lastSchemaFetchError: string | undefined = undefined - static catalogs: Map = new Map(); + static catalogs: Map = new Map() // map of fully qualified table name to tables - static tables: Map = new Map(); + static tables: Map = new Map() - static getTableNameList(catalogFilter : string | undefined, schemaFilter : string | undefined) : string[] { + static getTableNameList(catalogFilter: string | undefined, schemaFilter: string | undefined): string[] { // get list from catalogs, because tables may not be resolved - const tableNames : string[] = []; + const tableNames: string[] = [] for (const [key, value] of this.catalogs) { if (key === catalogFilter || !catalogFilter) { for (const [schemaName, schema] of value.getSchemas()) { if (schemaName === schemaFilter || !schemaFilter) { for (const [tableName, table] of schema.getTables()) { - tableNames.push(key + "." + schemaName + "." + tableName); + tableNames.push(key + '.' + schemaName + '.' + tableName) } } } } } - return tableNames; + return tableNames } - static isTableCached(tableRef : TableReference) { - if (this.tables.has(tableRef.fullyQualified)) - { + static isTableCached(tableRef: TableReference) { + if (this.tables.has(tableRef.fullyQualified)) { // check for columns - const table = this.tables.get(tableRef.fullyQualified); + const table = this.tables.get(tableRef.fullyQualified) if (table && table.getColumns().length > 0) { - return true; + return true } } - return false; + return false } - static getTableWithCache(tableRef : TableReference, callback : any) : Table | undefined { + static getTableWithCache(tableRef: TableReference, callback: any): Table | undefined { if (SchemaProvider.isTableCached(tableRef)) { - const table : Table | undefined = this.tables.get(tableRef.fullyQualified); + const table: Table | undefined = this.tables.get(tableRef.fullyQualified) if (callback) { - callback(table); + callback(table) } - return table; + return table } else { - SchemaProvider.getTableRefreshCache(tableRef, callback); - return undefined; + SchemaProvider.getTableRefreshCache(tableRef, callback) + return undefined } } - static getTableIfCached(tableRef : TableReference) { + static getTableIfCached(tableRef: TableReference) { if (SchemaProvider.isTableCached(tableRef)) { - return this.tables.get(tableRef.fullyQualified); + return this.tables.get(tableRef.fullyQualified) } // async operation to refresh cache but return null in the meantime - SchemaProvider.getTableRefreshCache(tableRef, (table : Table) => {}); - return null; + SchemaProvider.getTableRefreshCache(tableRef, (table: Table) => {}) + return null } - static populateCatalogsAndRefreshTableList(callback : any = null, errorCallback : any = null) { + static populateCatalogsAndRefreshTableList(callback: any = null, errorCallback: any = null) { // refresh catalogs - new TrinoQueryRunner().SetAllResultsCallback((results: any[], isError : boolean) => { - for (let i = 0; i < results.length; i++) { - const catalog : Catalog = new Catalog(results[i][0], results[i][1]); - if (!this.catalogs.has(catalog.getName())) { - this.catalogs.set(catalog.getName(), catalog); - } - this.lastSchemaFetchError = undefined; - callback(); - - // refresh tables and schemas for this catalog - new TrinoQueryRunner().SetAllResultsCallback((results: any[], isError : boolean) => { - for (let i = 0; i < results.length; i++) { - const schemaName = results[i][0]; - const tableName = results[i][1]; - const tableType = results[i][2]; - - const schema : Schema = catalog.getOrAdd(new Schema(schemaName)); - const table : Table = new Table(tableName); - schema.addTable(table); - - // add table to tables map - this.tables.set(catalog.getName() + "." + schema.getName() + "." + table.getName(), table); - } - - if (!isError) { - catalog.clearErrorMessage(); + new TrinoQueryRunner() + .SetAllResultsCallback((results: any[], isError: boolean) => { + for (let i = 0; i < results.length; i++) { + const catalog: Catalog = new Catalog(results[i][0], results[i][1]) + if (!this.catalogs.has(catalog.getName())) { + this.catalogs.set(catalog.getName(), catalog) } - callback(); - }) - .SetErrorMessageCallback((error: string) => { - catalog.setErrorMessage(error.toString()); - }) - .StartQuery("SELECT table_schema, table_name, table_type FROM " + catalog.getName() + ".information_schema.tables"); - } - }).SetErrorMessageCallback((error: string) => { - this.lastSchemaFetchError = error.toString(); - errorCallback(error.toString()); - }).StartQuery("select catalog_name, connector_name from system.metadata.catalogs"); + this.lastSchemaFetchError = undefined + callback() + + // refresh tables and schemas for this catalog + new TrinoQueryRunner() + .SetAllResultsCallback((results: any[], isError: boolean) => { + for (let i = 0; i < results.length; i++) { + const schemaName = results[i][0] + const tableName = results[i][1] + const tableType = results[i][2] + + const schema: Schema = catalog.getOrAdd(new Schema(schemaName)) + const table: Table = new Table(tableName) + schema.addTable(table) + + // add table to tables map + this.tables.set( + catalog.getName() + '.' + schema.getName() + '.' + table.getName(), + table + ) + } + + if (!isError) { + catalog.clearErrorMessage() + } + callback() + }) + .SetErrorMessageCallback((error: string) => { + catalog.setErrorMessage(error.toString()) + }) + .StartQuery( + 'SELECT table_schema, table_name, table_type FROM ' + + catalog.getName() + + '.information_schema.tables' + ) + } + }) + .SetErrorMessageCallback((error: string) => { + this.lastSchemaFetchError = error.toString() + errorCallback(error.toString()) + }) + .StartQuery('select catalog_name, connector_name from system.metadata.catalogs') } /* 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(); - query.SetAllResultsCallback((results: any[]) => { - // Create a temporary map to hold all tables in this schema - const schemaTables = new Map(); - - for (let i = 0; i < results.length; i++) { - const tableName = results[i][2]; // table_name - const columnName = results[i][3]; // column_name - const dataType = results[i][4]; // data_type - const comment = results[i][5]; // comment - const extraInfo = results[i][6]; // additional info if available - - let table = schemaTables.get(tableName); - if (!table) { - table = new Table(tableName); - schemaTables.set(tableName, table); + const query = new TrinoQueryRunner() + query + .SetAllResultsCallback((results: any[]) => { + // Create a temporary map to hold all tables in this schema + const schemaTables = new Map() + + for (let i = 0; i < results.length; i++) { + const tableName = results[i][2] // table_name + const columnName = results[i][3] // column_name + const dataType = results[i][4] // data_type + const comment = results[i][5] // comment + const extraInfo = results[i][6] // additional info if available + + let table = schemaTables.get(tableName) + if (!table) { + table = new Table(tableName) + schemaTables.set(tableName, table) + } + + table.getColumns().push(new Column(columnName, dataType, comment || '', extraInfo || '')) } - table.getColumns().push(new Column(columnName, dataType, comment || '', extraInfo || '')); - } + // Cache all tables we just loaded + schemaTables.forEach((table, tableName) => { + const fullPath = `${tableRef.catalogName}.${tableRef.schemaName}.${tableName}` + this.tables.set(fullPath, table) - // Cache all tables we just loaded - schemaTables.forEach((table, tableName) => { - const fullPath = `${tableRef.catalogName}.${tableRef.schemaName}.${tableName}`; - this.tables.set(fullPath, table); + const catalog = tableRef.getCatalog() + const schema = tableRef.getSchema() + if (catalog && schema) { + schema.addTable(table) + } + }) - const catalog = tableRef.getCatalog(); - const schema = tableRef.getSchema(); - if (catalog && schema) { - schema.addTable(table); + // If we found the requested table, use it + const requestedTable = schemaTables.get(tableRef.tableName) + if (requestedTable) { + callback(requestedTable) + } else { + // Fall back to DESCRIBE for this specific table + this.fallbackToDescribe(tableRef, callback) } - }); - - // If we found the requested table, use it - const requestedTable = schemaTables.get(tableRef.tableName); - if (requestedTable) { - callback(requestedTable); - } else { - // Fall back to DESCRIBE for this specific table - this.fallbackToDescribe(tableRef, callback); - } - }) - .SetErrorMessageCallback((error: string) => { - console.log('Error fetching table info:', error); - - // If information_schema query fails, fall back to DESCRIBE - this.fallbackToDescribe(tableRef, callback); - }); + }) + .SetErrorMessageCallback((error: string) => { + console.log('Error fetching table info:', error) + + // If information_schema query fails, fall back to DESCRIBE + this.fallbackToDescribe(tableRef, callback) + }) // Query information_schema for all columns in this schema query.StartQuery(` @@ -170,37 +180,38 @@ class SchemaProvider { comment FROM ${tableRef.catalogName}.information_schema.columns WHERE table_schema = '${tableRef.schemaName}' - `); + `) } private static fallbackToDescribe(tableRef: TableReference, callback: (table: Table) => void) { - const fallbackQuery = new TrinoQueryRunner(); - fallbackQuery.SetAllResultsCallback((results: any[]) => { - const table = new Table(tableRef.tableName); - const columns = table.getColumns(); - for (let i = 0; i < results.length; i++) { - columns.push(new Column(results[i][0], results[i][1], results[i][2], results[i][3])); - } - this.tables.set(tableRef.fullyQualified, table); - - const catalog = tableRef.getCatalog(); - const schema = tableRef.getSchema(); - if (catalog && schema) { - schema.addTable(table); - } + const fallbackQuery = new TrinoQueryRunner() + fallbackQuery + .SetAllResultsCallback((results: any[]) => { + const table = new Table(tableRef.tableName) + const columns = table.getColumns() + for (let i = 0; i < results.length; i++) { + columns.push(new Column(results[i][0], results[i][1], results[i][2], results[i][3])) + } + this.tables.set(tableRef.fullyQualified, table) - if (callback) { - callback(table); - } - }) - .SetErrorMessageCallback((error: string) => { - const table = new Table(tableRef.tableName); - table.setError(error); - callback(table); - }); - - fallbackQuery.StartQuery(`DESCRIBE ${tableRef.fullyQualified}`); + const catalog = tableRef.getCatalog() + const schema = tableRef.getSchema() + if (catalog && schema) { + schema.addTable(table) + } + + if (callback) { + callback(table) + } + }) + .SetErrorMessageCallback((error: string) => { + const table = new Table(tableRef.tableName) + table.setError(error) + callback(table) + }) + + fallbackQuery.StartQuery(`DESCRIBE ${tableRef.fullyQualified}`) } } -export default SchemaProvider; \ No newline at end of file +export default SchemaProvider diff --git a/precise/src/sql/SpecialHighlight.ts b/precise/src/sql/SpecialHighlight.ts index 8d4deb7..3a87d7b 100644 --- a/precise/src/sql/SpecialHighlight.ts +++ b/precise/src/sql/SpecialHighlight.ts @@ -1,71 +1,80 @@ -import * as monaco from 'monaco-editor/esm/vs/editor/editor.api'; -import NamedQuery from './NamedQuery'; -import TrinoQueryRunner from '../AsyncTrinoClient'; -import SchemaProvider from './SchemaProvider'; -import TableReference from '../schema/TableReference'; +import * as monaco from 'monaco-editor/esm/vs/editor/editor.api' +import NamedQuery from './NamedQuery' +import SchemaProvider from './SchemaProvider' +import TableReference from '../schema/TableReference' // class that describes the type of special highlight, containing the start and end position, and kind of highlight, and ast location - class SpecialHighlight { - startLineNumber: number; - startColumn: number; - endLineNumber: number; - endColumn: number; - kind: string; - ast: any; - catalog?: string; - schema?: string; + startLineNumber: number + startColumn: number + endLineNumber: number + endColumn: number + kind: string + ast: any + catalog?: string + schema?: string - constructor(startLineNumber: number, startColumn: number, endLineNumber: number, endColumn: number, kind: string, ast: any, catalog?: string, schema?: string) { - this.startLineNumber = startLineNumber; - this.startColumn = startColumn; - this.endLineNumber = endLineNumber; - this.endColumn = endColumn; - this.kind = kind; - this.ast = ast; - this.catalog = catalog; - this.schema = schema; + constructor( + startLineNumber: number, + startColumn: number, + endLineNumber: number, + endColumn: number, + kind: string, + ast: any, + catalog?: string, + schema?: string + ) { + this.startLineNumber = startLineNumber + this.startColumn = startColumn + this.endLineNumber = endLineNumber + this.endColumn = endColumn + this.kind = kind + this.ast = ast + this.catalog = catalog + this.schema = schema } getDecoration(namedQueries: Map) { // check if the highlight is a named query - var inlineClassName : String = this.kind; + let inlineClassName: string = this.kind if (namedQueries.has(this.ast.getText())) { - inlineClassName = "relationReference"; + inlineClassName = 'relationReference' } // Get the table reference with context awareness - let tableReference: TableReference | undefined; - const tableName = this.ast.getText(); - + let tableReference: TableReference | undefined + const tableName = this.ast.getText() + if (TableReference.isFullyQualified(tableName)) { // If fully qualified, use that directly - tableReference = TableReference.fromFullyQualified(tableName); + tableReference = TableReference.fromFullyQualified(tableName) } else if (this.catalog && this.schema) { // If not fully qualified but we have catalog and schema context, use those - tableReference = new TableReference(this.catalog, this.schema, tableName); + tableReference = new TableReference(this.catalog, this.schema, tableName) } - let hoverMessage = ""; + let hoverMessage = '' if (tableReference) { - const table = SchemaProvider.getTableIfCached(tableReference); + const table = SchemaProvider.getTableIfCached(tableReference) if (table) { - hoverMessage = table.getFullSchemaAsString(); + hoverMessage = table.getFullSchemaAsString() } } return { range: new monaco.Range(this.startLineNumber, this.startColumn + 1, this.endLineNumber, this.endColumn + 2), - options: { + options: { inlineClassName: inlineClassName, - hoverMessage: [{ - isTrusted: true, - supportHtml: true, - value: hoverMessage - }] - } - }; + hoverMessage: [ + { + isTrusted: true, + supportHtml: true, + value: hoverMessage, + }, + ], + }, + } } } -export default SpecialHighlight; \ No newline at end of file +export default SpecialHighlight diff --git a/precise/src/sql/SqlBaseErrorListener.ts b/precise/src/sql/SqlBaseErrorListener.ts index ab818ed..29c7946 100644 --- a/precise/src/sql/SqlBaseErrorListener.ts +++ b/precise/src/sql/SqlBaseErrorListener.ts @@ -1,19 +1,25 @@ -import { ANTLRErrorListener } from 'antlr4ng'; - -class SqlBaseErrorListener implements ANTLRErrorListener { +import { ANTLRErrorListener } from 'antlr4ng' - markers: any[]; +class SqlBaseErrorListener implements ANTLRErrorListener { + markers: any[] constructor() { - this.markers = []; + this.markers = [] } getMarkers() { - return this.markers; + return this.markers } //syntaxError(recognizer: Recognizer, offendingSymbol: S | null, line: number, charPositionInLine: number, msg: string, e: RecognitionException | null): void; - syntaxError(recognizer: any, offendingSymbol: any, line: number, charPositionInLine: number, msg: string, e: any): void { + syntaxError( + recognizer: any, + offendingSymbol: any, + line: number, + charPositionInLine: number, + msg: string, + e: any + ): void { // Handle the error (e.g., log it, add it to a list, etc.) this.markers.push({ startLineNumber: line, @@ -21,27 +27,49 @@ class SqlBaseErrorListener implements ANTLRErrorListener { endLineNumber: line, endColumn: offendingSymbol.stop + 2, message: msg, - severity: 8 - }); + severity: 8, + }) } //reportAmbiguity(recognizer: Parser, dfa: DFA, startIndex: number, stopIndex: number, exact: boolean, ambigAlts: BitSet | undefined, configs: ATNConfigSet): void; - reportAmbiguity(recognizer: any, dfa: any, startIndex: number, stopIndex: number, exact: boolean, ambigAlts: any, configs: any): void { + reportAmbiguity( + recognizer: any, + dfa: any, + startIndex: number, + stopIndex: number, + exact: boolean, + ambigAlts: any, + configs: any + ): void { // Handle the ambiguity (e.g., log it, add it to a list, etc.) //console.error(`Ambiguity at indexes ${startIndex}-${stopIndex}`); } //reportContextSensitivity(recognizer: Parser, dfa: DFA, startIndex: number, stopIndex: number, prediction: number, configs: ATNConfigSet): void; - reportContextSensitivity(recognizer: any, dfa: any, startIndex: number, stopIndex: number, prediction: number, configs: any): void { + reportContextSensitivity( + recognizer: any, + dfa: any, + startIndex: number, + stopIndex: number, + prediction: number, + configs: any + ): void { // Handle the context sensitivity (e.g., log it, add it to a list, etc.) //console.error(`Context sensitivity at indexes ${startIndex}-${stopIndex}`); } // reportAttemptingFullContext(recognizer: Parser, dfa: DFA, startIndex: number, stopIndex: number, conflictingAlts: BitSet | undefined, configs: ATNConfigSet): void; - reportAttemptingFullContext(recognizer: any, dfa: any, startIndex: number, stopIndex: number, conflictingAlts: any, configs: any): void { + reportAttemptingFullContext( + recognizer: any, + dfa: any, + startIndex: number, + stopIndex: number, + conflictingAlts: any, + configs: any + ): void { // Handle the full context attempt (e.g., log it, add it to a list, etc.) //console.error(`Full context attempt at indexes ${startIndex}-${stopIndex}`); } } -export default SqlBaseErrorListener; \ No newline at end of file +export default SqlBaseErrorListener diff --git a/precise/src/sql/SqlBaseListenerImpl.ts b/precise/src/sql/SqlBaseListenerImpl.ts index e42f27d..f63043d 100644 --- a/precise/src/sql/SqlBaseListenerImpl.ts +++ b/precise/src/sql/SqlBaseListenerImpl.ts @@ -1,126 +1,127 @@ -import { SqlBaseListener } from '../generated/lexer/SqlBase.g4/SqlBaseListener'; -import SpecialHighlight from './SpecialHighlight'; -import { ColumnAliasesContext, ColumnReferenceContext, IdentifierContext, QueryColumnContext, QuerySpecificationContext, SelectSingleContext, TableNameContext, UnquotedIdentifierContext } from '../generated/lexer/SqlBase.g4/SqlBaseParser'; -import NamedQuery from './NamedQuery'; -import StatementDescriptor from './StatementDescriptor'; -import SchemaProvider from './SchemaProvider'; -import TableReference from '../schema/TableReference'; +import { SqlBaseListener } from '../generated/lexer/SqlBase.g4/SqlBaseListener' +import SpecialHighlight from './SpecialHighlight' +import { + IdentifierContext, + QuerySpecificationContext, + SelectSingleContext, + TableNameContext, + UnquotedIdentifierContext, +} from '../generated/lexer/SqlBase.g4/SqlBaseParser' +import NamedQuery from './NamedQuery' +import StatementDescriptor from './StatementDescriptor' +import SchemaProvider from './SchemaProvider' +import TableReference from '../schema/TableReference' + +class SqlBaseListenerImpl extends SqlBaseListener { + specialHighlights: SpecialHighlight[] + tableColumns: Map = new Map() + namedQueries: Map = new Map() + currentColumns: string[] = [] + currentTableNameContext: string = '' + statements: StatementDescriptor[] = [] -class SqlBaseListenerImpl extends SqlBaseListener -{ - specialHighlights: SpecialHighlight[]; - tableColumns: Map = new Map(); - namedQueries: Map = new Map(); - currentColumns : string[] = []; - currentTableNameContext : string = ""; - statements : StatementDescriptor[] = []; - // Add properties for current catalog and schema - currentCatalog?: string; - currentSchema?: string; + currentCatalog?: string + currentSchema?: string constructor(catalog?: string, schema?: string) { - super(); - this.specialHighlights = []; - this.namedQueries = new Map(); - this.currentCatalog = catalog; - this.currentSchema = schema; + super() + this.specialHighlights = [] + this.namedQueries = new Map() + this.currentCatalog = catalog + this.currentSchema = schema } - public enterQualifiedName = (ctx: any) => { - } + public enterQualifiedName = (ctx: any) => {} public exitQualifiedName = (ctx: any) => { if (ctx.parent instanceof TableNameContext) { // Create SpecialHighlight with current catalog and schema - const currentQualifiedName : SpecialHighlight = new SpecialHighlight( - ctx.start.line, - ctx.start.column, - ctx.stop.line, - ctx.stop.column + (ctx.stop.stop - ctx.stop.start), - "qualifiedName", + const currentQualifiedName: SpecialHighlight = new SpecialHighlight( + ctx.start.line, + ctx.start.column, + ctx.stop.line, + ctx.stop.column + (ctx.stop.stop - ctx.stop.start), + 'qualifiedName', ctx, this.currentCatalog, this.currentSchema - ); - - this.specialHighlights.push(currentQualifiedName); - const name = ctx.getText(); - + ) + + this.specialHighlights.push(currentQualifiedName) + const name = ctx.getText() + // Handle table references considering current catalog and schema - let tableRef: TableReference; + let tableRef: TableReference if (TableReference.isFullyQualified(name)) { - tableRef = TableReference.fromFullyQualified(name); + tableRef = TableReference.fromFullyQualified(name) } else if (this.currentCatalog && this.currentSchema) { - tableRef = new TableReference(this.currentCatalog, this.currentSchema, name); + tableRef = new TableReference(this.currentCatalog, this.currentSchema, name) } else { - // If we don't have enough context and it's not fully qualified, + // If we don't have enough context and it's not fully qualified, // we'll treat it as is and let SchemaProvider handle it - tableRef = TableReference.fromFullyQualified(name); + tableRef = TableReference.fromFullyQualified(name) } - - this.currentTableNameContext = name; + + this.currentTableNameContext = name // Try to populate the cache - SchemaProvider.getTableIfCached(tableRef); - this.tableColumns.set(name, this.currentColumns); - this.currentColumns = []; + SchemaProvider.getTableIfCached(tableRef) + this.tableColumns.set(name, this.currentColumns) + this.currentColumns = [] } } public enterQuerySpecification = (ctx: QuerySpecificationContext) => { - this.currentTableNameContext = ""; + this.currentTableNameContext = '' } - + public exitQuerySpecification = (ctx: QuerySpecificationContext) => { - if (this.currentTableNameContext !== "") { - const tableName = this.currentTableNameContext; - this.statements.push(new StatementDescriptor(tableName, ctx.start, ctx.stop)); + if (this.currentTableNameContext !== '') { + const tableName = this.currentTableNameContext + this.statements.push(new StatementDescriptor(tableName, ctx.start, ctx.stop)) } } // The name of a CTE public exitNamedQuery = (ctx: any) => { if (ctx.children.length > 0 && ctx.children[0] instanceof IdentifierContext) { - const name = ctx.children[0].getText(); - this.namedQueries.set(name, new NamedQuery(name, ctx)); - this.tableColumns.set(name, this.currentColumns); - this.currentColumns = []; + const name = ctx.children[0].getText() + this.namedQueries.set(name, new NamedQuery(name, ctx)) + this.tableColumns.set(name, this.currentColumns) + this.currentColumns = [] } } // The name of an aliased relation public exitAliasedRelation = (ctx: any) => { if (ctx.children.length > 2 && ctx.children[2] instanceof IdentifierContext) { - const name = ctx.children[2].getText(); - this.namedQueries.set(name, new NamedQuery(name, ctx)); - this.tableColumns.set(name, this.currentColumns); - this.currentColumns = []; + const name = ctx.children[2].getText() + this.namedQueries.set(name, new NamedQuery(name, ctx)) + this.tableColumns.set(name, this.currentColumns) + this.currentColumns = [] } } - - public exitUnquotedIdentifier = (ctx: UnquotedIdentifierContext) => - { + + public exitUnquotedIdentifier = (ctx: UnquotedIdentifierContext) => { // to know if this is an aliased column we need to go up the tree to the SelectSingleContext // if this has an alias, it will have multiple children the last one being the alias, if not it will have only one child - var current : any = ctx; + let current: any = ctx while (current && !(current instanceof SelectSingleContext)) { - current = current.parent; + current = current.parent } if (current) { if (current.children.length > 1) { - const alias = current.children[current.children.length - 1].getText(); - this.currentColumns.push(alias); - } - else { - this.currentColumns.push(ctx.getText()); + const alias = current.children[current.children.length - 1].getText() + this.currentColumns.push(alias) + } else { + this.currentColumns.push(ctx.getText()) } } } - public getDecorations() - { - return this.specialHighlights.map((highlight) => highlight.getDecoration(this.namedQueries)); + public getDecorations() { + return this.specialHighlights.map((highlight) => highlight.getDecoration(this.namedQueries)) } } -export default SqlBaseListenerImpl; \ No newline at end of file +export default SqlBaseListenerImpl diff --git a/precise/src/sql/StatementDescriptor.ts b/precise/src/sql/StatementDescriptor.ts index 38cb931..df8b21a 100644 --- a/precise/src/sql/StatementDescriptor.ts +++ b/precise/src/sql/StatementDescriptor.ts @@ -1,14 +1,13 @@ class StatementDescriptor { + tableName: string + start: any + end: any - tableName : string; - start: any; - end: any; - - constructor(tableName : string, start : any, stop : any) { - this.tableName = tableName; - this.start = start; - this.end = stop; + constructor(tableName: string, start: any, stop: any) { + this.tableName = tableName + this.start = start + this.end = stop } } -export default StatementDescriptor; \ No newline at end of file +export default StatementDescriptor diff --git a/precise/src/sql/TokenMap.ts b/precise/src/sql/TokenMap.ts index 1b89ce7..a5ec7f8 100644 --- a/precise/src/sql/TokenMap.ts +++ b/precise/src/sql/TokenMap.ts @@ -1,327 +1,326 @@ -import { SqlBaseLexer } from '../generated/lexer/SqlBase.g4/SqlBaseLexer'; - -export const tokenMap = { - [SqlBaseLexer.T__0]: 'delimiter', - [SqlBaseLexer.T__1]: 'delimiter', - [SqlBaseLexer.T__2]: 'delimiter', - [SqlBaseLexer.T__3]: 'delimiter', - [SqlBaseLexer.T__4]: 'delimiter', - [SqlBaseLexer.T__5]: 'delimiter', - [SqlBaseLexer.T__6]: 'delimiter', - [SqlBaseLexer.T__7]: 'delimiter', - [SqlBaseLexer.T__8]: 'delimiter', - [SqlBaseLexer.T__9]: 'delimiter', - [SqlBaseLexer.T__10]: 'delimiter', - [SqlBaseLexer.T__11]: 'delimiter', - [SqlBaseLexer.T__12]: 'delimiter', - [SqlBaseLexer.T__13]: 'delimiter', - [SqlBaseLexer.T__14]: 'delimiter', - [SqlBaseLexer.T__15]: 'delimiter', - [SqlBaseLexer.T__16]: 'delimiter', - [SqlBaseLexer.ABSENT]: 'keyword', - [SqlBaseLexer.ADD]: 'keyword', - [SqlBaseLexer.ADMIN]: 'keyword', - [SqlBaseLexer.AFTER]: 'keyword', - [SqlBaseLexer.ALL]: 'keyword', - [SqlBaseLexer.ALTER]: 'keyword', - [SqlBaseLexer.ANALYZE]: 'keyword', - [SqlBaseLexer.AND]: 'keyword', - [SqlBaseLexer.ANY]: 'keyword', - [SqlBaseLexer.ARRAY]: 'keyword', - [SqlBaseLexer.AS]: 'keyword', - [SqlBaseLexer.ASC]: 'keyword', - [SqlBaseLexer.AT]: 'keyword', - [SqlBaseLexer.AUTHORIZATION]: 'keyword', - [SqlBaseLexer.BERNOULLI]: 'keyword', - [SqlBaseLexer.BETWEEN]: 'keyword', - [SqlBaseLexer.BOTH]: 'keyword', - [SqlBaseLexer.BY]: 'keyword', - [SqlBaseLexer.CALL]: 'keyword', - [SqlBaseLexer.CASCADE]: 'keyword', - [SqlBaseLexer.CASE]: 'keyword', - [SqlBaseLexer.CAST]: 'keyword', - [SqlBaseLexer.CATALOG]: 'keyword', - [SqlBaseLexer.CATALOGS]: 'keyword', - [SqlBaseLexer.COLUMN]: 'keyword', - [SqlBaseLexer.COLUMNS]: 'keyword', - [SqlBaseLexer.COMMENT]: 'keyword', - [SqlBaseLexer.COMMIT]: 'keyword', - [SqlBaseLexer.COMMITTED]: 'keyword', - [SqlBaseLexer.CONDITIONAL]: 'keyword', - [SqlBaseLexer.CONSTRAINT]: 'keyword', - [SqlBaseLexer.COUNT]: 'keyword', - [SqlBaseLexer.COPARTITION]: 'keyword', - [SqlBaseLexer.CREATE]: 'keyword', - [SqlBaseLexer.CROSS]: 'keyword', - [SqlBaseLexer.CUBE]: 'keyword', - [SqlBaseLexer.CURRENT]: 'keyword', - [SqlBaseLexer.CURRENT_CATALOG]: 'keyword', - [SqlBaseLexer.CURRENT_DATE]: 'keyword', - [SqlBaseLexer.CURRENT_PATH]: 'keyword', - [SqlBaseLexer.CURRENT_ROLE]: 'keyword', - [SqlBaseLexer.CURRENT_SCHEMA]: 'keyword', - [SqlBaseLexer.CURRENT_TIME]: 'keyword', - [SqlBaseLexer.CURRENT_TIMESTAMP]: 'keyword', - [SqlBaseLexer.CURRENT_USER]: 'keyword', - [SqlBaseLexer.DATA]: 'keyword', - [SqlBaseLexer.DATE]: 'keyword', - [SqlBaseLexer.DAY]: 'keyword', - [SqlBaseLexer.DEALLOCATE]: 'keyword', - [SqlBaseLexer.DEFAULT]: 'keyword', - [SqlBaseLexer.DEFINE]: 'keyword', - [SqlBaseLexer.DEFINER]: 'keyword', - [SqlBaseLexer.DELETE]: 'keyword', - [SqlBaseLexer.DENY]: 'keyword', - [SqlBaseLexer.DESC]: 'keyword', - [SqlBaseLexer.DESCRIBE]: 'keyword', - [SqlBaseLexer.DESCRIPTOR]: 'keyword', - [SqlBaseLexer.DISTINCT]: 'keyword', - [SqlBaseLexer.DISTRIBUTED]: 'keyword', - [SqlBaseLexer.DOUBLE]: 'keyword', - [SqlBaseLexer.DROP]: 'keyword', - [SqlBaseLexer.ELSE]: 'keyword', - [SqlBaseLexer.EMPTY]: 'keyword', - [SqlBaseLexer.ENCODING]: 'keyword', - [SqlBaseLexer.END]: 'keyword', - [SqlBaseLexer.ERROR]: 'keyword', - [SqlBaseLexer.ESCAPE]: 'keyword', - [SqlBaseLexer.EXCEPT]: 'keyword', - [SqlBaseLexer.EXCLUDING]: 'keyword', - [SqlBaseLexer.EXECUTE]: 'keyword', - [SqlBaseLexer.EXISTS]: 'keyword', - [SqlBaseLexer.EXPLAIN]: 'keyword', - [SqlBaseLexer.EXTRACT]: 'keyword', - [SqlBaseLexer.FALSE]: 'keyword', - [SqlBaseLexer.FETCH]: 'keyword', - [SqlBaseLexer.FILTER]: 'keyword', - [SqlBaseLexer.FINAL]: 'keyword', - [SqlBaseLexer.FIRST]: 'keyword', - [SqlBaseLexer.FOLLOWING]: 'keyword', - [SqlBaseLexer.FOR]: 'keyword', - [SqlBaseLexer.FORMAT]: 'keyword', - [SqlBaseLexer.FROM]: 'keyword', - [SqlBaseLexer.FULL]: 'keyword', - [SqlBaseLexer.FUNCTIONS]: 'keyword', - [SqlBaseLexer.GRACE]: 'keyword', - [SqlBaseLexer.GRANT]: 'keyword', - [SqlBaseLexer.GRANTED]: 'keyword', - [SqlBaseLexer.GRANTS]: 'keyword', - [SqlBaseLexer.GRAPHVIZ]: 'keyword', - [SqlBaseLexer.GROUP]: 'keyword', - [SqlBaseLexer.GROUPING]: 'keyword', - [SqlBaseLexer.GROUPS]: 'keyword', - [SqlBaseLexer.HAVING]: 'keyword', - [SqlBaseLexer.HOUR]: 'keyword', - [SqlBaseLexer.IF]: 'keyword', - [SqlBaseLexer.IGNORE]: 'keyword', - [SqlBaseLexer.IMMEDIATE]: 'keyword', - [SqlBaseLexer.IN]: 'keyword', - [SqlBaseLexer.INCLUDING]: 'keyword', - [SqlBaseLexer.INITIAL]: 'keyword', - [SqlBaseLexer.INNER]: 'keyword', - [SqlBaseLexer.INPUT]: 'keyword', - [SqlBaseLexer.INSERT]: 'keyword', - [SqlBaseLexer.INTERSECT]: 'keyword', - [SqlBaseLexer.INTERVAL]: 'keyword', - [SqlBaseLexer.INTO]: 'keyword', - [SqlBaseLexer.INVOKER]: 'keyword', - [SqlBaseLexer.IO]: 'keyword', - [SqlBaseLexer.IS]: 'keyword', - [SqlBaseLexer.ISOLATION]: 'keyword', - [SqlBaseLexer.JOIN]: 'keyword', - [SqlBaseLexer.JSON]: 'keyword', - [SqlBaseLexer.JSON_ARRAY]: 'keyword', - [SqlBaseLexer.JSON_EXISTS]: 'keyword', - [SqlBaseLexer.JSON_OBJECT]: 'keyword', - [SqlBaseLexer.JSON_QUERY]: 'keyword', - [SqlBaseLexer.JSON_TABLE]: 'keyword', - [SqlBaseLexer.JSON_VALUE]: 'keyword', - [SqlBaseLexer.KEEP]: 'keyword', - [SqlBaseLexer.KEY]: 'keyword', - [SqlBaseLexer.KEYS]: 'keyword', - [SqlBaseLexer.LAST]: 'keyword', - [SqlBaseLexer.LATERAL]: 'keyword', - [SqlBaseLexer.LEADING]: 'keyword', - [SqlBaseLexer.LEFT]: 'keyword', - [SqlBaseLexer.LEVEL]: 'keyword', - [SqlBaseLexer.LIKE]: 'keyword', - [SqlBaseLexer.LIMIT]: 'keyword', - [SqlBaseLexer.LISTAGG]: 'keyword', - [SqlBaseLexer.LOCAL]: 'keyword', - [SqlBaseLexer.LOCALTIME]: 'keyword', - [SqlBaseLexer.LOCALTIMESTAMP]: 'keyword', - [SqlBaseLexer.LOGICAL]: 'keyword', - [SqlBaseLexer.MAP]: 'keyword', - [SqlBaseLexer.MATCH]: 'keyword', - [SqlBaseLexer.MATCHED]: 'keyword', - [SqlBaseLexer.MATCHES]: 'keyword', - [SqlBaseLexer.MATCH_RECOGNIZE]: 'keyword', - [SqlBaseLexer.MATERIALIZED]: 'keyword', - [SqlBaseLexer.MEASURES]: 'keyword', - [SqlBaseLexer.MERGE]: 'keyword', - [SqlBaseLexer.MINUTE]: 'keyword', - [SqlBaseLexer.MONTH]: 'keyword', - [SqlBaseLexer.NATURAL]: 'keyword', - [SqlBaseLexer.NESTED]: 'keyword', - [SqlBaseLexer.NEXT]: 'keyword', - [SqlBaseLexer.NFC]: 'keyword', - [SqlBaseLexer.NFD]: 'keyword', - [SqlBaseLexer.NFKC]: 'keyword', - [SqlBaseLexer.NFKD]: 'keyword', - [SqlBaseLexer.NO]: 'keyword', - [SqlBaseLexer.NONE]: 'keyword', - [SqlBaseLexer.NORMALIZE]: 'keyword', - [SqlBaseLexer.NOT]: 'keyword', - [SqlBaseLexer.NULL]: 'keyword', - [SqlBaseLexer.NULLIF]: 'keyword', - [SqlBaseLexer.NULLS]: 'keyword', - [SqlBaseLexer.OBJECT]: 'keyword', - [SqlBaseLexer.OF]: 'keyword', - [SqlBaseLexer.OFFSET]: 'keyword', - [SqlBaseLexer.OMIT]: 'keyword', - [SqlBaseLexer.ON]: 'keyword', - [SqlBaseLexer.ONE]: 'keyword', - [SqlBaseLexer.ONLY]: 'keyword', - [SqlBaseLexer.OPTION]: 'keyword', - [SqlBaseLexer.OR]: 'keyword', - [SqlBaseLexer.ORDER]: 'keyword', - [SqlBaseLexer.ORDINALITY]: 'keyword', - [SqlBaseLexer.OUTER]: 'keyword', - [SqlBaseLexer.OUTPUT]: 'keyword', - [SqlBaseLexer.OVER]: 'keyword', - [SqlBaseLexer.OVERFLOW]: 'keyword', - [SqlBaseLexer.PARTITION]: 'keyword', - [SqlBaseLexer.PARTITIONS]: 'keyword', - [SqlBaseLexer.PASSING]: 'keyword', - [SqlBaseLexer.PAST]: 'keyword', - [SqlBaseLexer.PATH]: 'keyword', - [SqlBaseLexer.PATTERN]: 'keyword', - [SqlBaseLexer.PER]: 'keyword', - [SqlBaseLexer.PERIOD]: 'keyword', - [SqlBaseLexer.PERMUTE]: 'keyword', - [SqlBaseLexer.PLAN]: 'keyword', - [SqlBaseLexer.POSITION]: 'keyword', - [SqlBaseLexer.PRECEDING]: 'keyword', - [SqlBaseLexer.PRECISION]: 'keyword', - [SqlBaseLexer.PREPARE]: 'keyword', - [SqlBaseLexer.PRIVILEGES]: 'keyword', - [SqlBaseLexer.PROPERTIES]: 'keyword', - [SqlBaseLexer.PRUNE]: 'keyword', - [SqlBaseLexer.QUOTES]: 'keyword', - [SqlBaseLexer.RANGE]: 'keyword', - [SqlBaseLexer.READ]: 'keyword', - [SqlBaseLexer.RECURSIVE]: 'keyword', - [SqlBaseLexer.REFRESH]: 'keyword', - [SqlBaseLexer.RENAME]: 'keyword', - [SqlBaseLexer.REPEATABLE]: 'keyword', - [SqlBaseLexer.REPLACE]: 'keyword', - [SqlBaseLexer.RESET]: 'keyword', - [SqlBaseLexer.RESPECT]: 'keyword', - [SqlBaseLexer.RESTRICT]: 'keyword', - [SqlBaseLexer.RETURNING]: 'keyword', - [SqlBaseLexer.REVOKE]: 'keyword', - [SqlBaseLexer.RIGHT]: 'keyword', - [SqlBaseLexer.ROLE]: 'keyword', - [SqlBaseLexer.ROLES]: 'keyword', - [SqlBaseLexer.ROLLBACK]: 'keyword', - [SqlBaseLexer.ROLLUP]: 'keyword', - [SqlBaseLexer.ROW]: 'keyword', - [SqlBaseLexer.ROWS]: 'keyword', - [SqlBaseLexer.RUNNING]: 'keyword', - [SqlBaseLexer.SCALAR]: 'keyword', - [SqlBaseLexer.SCHEMA]: 'keyword', - [SqlBaseLexer.SCHEMAS]: 'keyword', - [SqlBaseLexer.SECOND]: 'keyword', - [SqlBaseLexer.SECURITY]: 'keyword', - [SqlBaseLexer.SEEK]: 'keyword', - [SqlBaseLexer.SELECT]: 'keyword', - [SqlBaseLexer.SERIALIZABLE]: 'keyword', - [SqlBaseLexer.SESSION]: 'keyword', - [SqlBaseLexer.SET]: 'keyword', - [SqlBaseLexer.SETS]: 'keyword', - [SqlBaseLexer.SHOW]: 'keyword', - [SqlBaseLexer.SOME]: 'keyword', - [SqlBaseLexer.START]: 'keyword', - [SqlBaseLexer.STATS]: 'keyword', - [SqlBaseLexer.SUBSET]: 'keyword', - [SqlBaseLexer.SUBSTRING]: 'keyword', - [SqlBaseLexer.SYSTEM]: 'keyword', - [SqlBaseLexer.TABLE]: 'keyword', - [SqlBaseLexer.TABLES]: 'keyword', - [SqlBaseLexer.TABLESAMPLE]: 'keyword', - [SqlBaseLexer.TEXT]: 'keyword', - [SqlBaseLexer.TEXT_STRING]: 'string', - [SqlBaseLexer.THEN]: 'keyword', - [SqlBaseLexer.TIES]: 'keyword', - [SqlBaseLexer.TIME]: 'keyword', - [SqlBaseLexer.TIMESTAMP]: 'keyword', - [SqlBaseLexer.TO]: 'keyword', - [SqlBaseLexer.TRAILING]: 'keyword', - [SqlBaseLexer.TRANSACTION]: 'keyword', - [SqlBaseLexer.TRIM]: 'keyword', - [SqlBaseLexer.TRUE]: 'keyword', - [SqlBaseLexer.TRUNCATE]: 'keyword', - [SqlBaseLexer.TRY_CAST]: 'keyword', - [SqlBaseLexer.TYPE]: 'keyword', - [SqlBaseLexer.UESCAPE]: 'keyword', - [SqlBaseLexer.UNBOUNDED]: 'keyword', - [SqlBaseLexer.UNCOMMITTED]: 'keyword', - [SqlBaseLexer.UNCONDITIONAL]: 'keyword', - [SqlBaseLexer.UNION]: 'keyword', - [SqlBaseLexer.UNIQUE]: 'keyword', - [SqlBaseLexer.UNKNOWN]: 'keyword', - [SqlBaseLexer.UNMATCHED]: 'keyword', - [SqlBaseLexer.UNNEST]: 'keyword', - [SqlBaseLexer.UPDATE]: 'keyword', - [SqlBaseLexer.USE]: 'keyword', - [SqlBaseLexer.USER]: 'keyword', - [SqlBaseLexer.USING]: 'keyword', - [SqlBaseLexer.UTF16]: 'keyword', - [SqlBaseLexer.UTF32]: 'keyword', - [SqlBaseLexer.UTF8]: 'keyword', - [SqlBaseLexer.VALIDATE]: 'keyword', - [SqlBaseLexer.VALUE]: 'keyword', - [SqlBaseLexer.VALUES]: 'keyword', - [SqlBaseLexer.VERBOSE]: 'keyword', - [SqlBaseLexer.VERSION]: 'keyword', - [SqlBaseLexer.VIEW]: 'keyword', - [SqlBaseLexer.WHEN]: 'keyword', - [SqlBaseLexer.WHERE]: 'keyword', - [SqlBaseLexer.WINDOW]: 'keyword', - [SqlBaseLexer.WITH]: 'keyword', - [SqlBaseLexer.WITHIN]: 'keyword', - [SqlBaseLexer.WITHOUT]: 'keyword', - [SqlBaseLexer.WORK]: 'keyword', - [SqlBaseLexer.WRAPPER]: 'keyword', - [SqlBaseLexer.WRITE]: 'keyword', - [SqlBaseLexer.YEAR]: 'keyword', - [SqlBaseLexer.ZONE]: 'keyword', - [SqlBaseLexer.EQ]: 'operator', - [SqlBaseLexer.NEQ]: 'operator', - [SqlBaseLexer.LT]: 'operator', - [SqlBaseLexer.LTE]: 'operator', - [SqlBaseLexer.GT]: 'operator', - [SqlBaseLexer.GTE]: 'operator', - [SqlBaseLexer.PLUS]: 'operator', - [SqlBaseLexer.MINUS]: 'operator', - [SqlBaseLexer.ASTERISK]: 'operator', - [SqlBaseLexer.SLASH]: 'operator', - [SqlBaseLexer.PERCENT]: 'operator', - [SqlBaseLexer.CONCAT]: 'operator', - [SqlBaseLexer.QUESTION_MARK]: 'operator', - [SqlBaseLexer.STRING]: 'string', - [SqlBaseLexer.UNICODE_STRING]: 'string', - [SqlBaseLexer.BINARY_LITERAL]: 'string', - [SqlBaseLexer.INTEGER_VALUE]: 'number', - [SqlBaseLexer.DECIMAL_VALUE]: 'number', - [SqlBaseLexer.DOUBLE_VALUE]: 'number', - [SqlBaseLexer.IDENTIFIER]: 'identifier', - [SqlBaseLexer.DIGIT_IDENTIFIER]: 'identifier', - [SqlBaseLexer.QUOTED_IDENTIFIER]: 'identifier', - [SqlBaseLexer.BACKQUOTED_IDENTIFIER]: 'identifier', - [SqlBaseLexer.SIMPLE_COMMENT]: 'comment', - [SqlBaseLexer.BRACKETED_COMMENT]: 'comment', - [SqlBaseLexer.WS]: 'whitespace', - [SqlBaseLexer.UNRECOGNIZED]: 'invalid', - }; +import { SqlBaseLexer } from '../generated/lexer/SqlBase.g4/SqlBaseLexer' +export const tokenMap = { + [SqlBaseLexer.T__0]: 'delimiter', + [SqlBaseLexer.T__1]: 'delimiter', + [SqlBaseLexer.T__2]: 'delimiter', + [SqlBaseLexer.T__3]: 'delimiter', + [SqlBaseLexer.T__4]: 'delimiter', + [SqlBaseLexer.T__5]: 'delimiter', + [SqlBaseLexer.T__6]: 'delimiter', + [SqlBaseLexer.T__7]: 'delimiter', + [SqlBaseLexer.T__8]: 'delimiter', + [SqlBaseLexer.T__9]: 'delimiter', + [SqlBaseLexer.T__10]: 'delimiter', + [SqlBaseLexer.T__11]: 'delimiter', + [SqlBaseLexer.T__12]: 'delimiter', + [SqlBaseLexer.T__13]: 'delimiter', + [SqlBaseLexer.T__14]: 'delimiter', + [SqlBaseLexer.T__15]: 'delimiter', + [SqlBaseLexer.T__16]: 'delimiter', + [SqlBaseLexer.ABSENT]: 'keyword', + [SqlBaseLexer.ADD]: 'keyword', + [SqlBaseLexer.ADMIN]: 'keyword', + [SqlBaseLexer.AFTER]: 'keyword', + [SqlBaseLexer.ALL]: 'keyword', + [SqlBaseLexer.ALTER]: 'keyword', + [SqlBaseLexer.ANALYZE]: 'keyword', + [SqlBaseLexer.AND]: 'keyword', + [SqlBaseLexer.ANY]: 'keyword', + [SqlBaseLexer.ARRAY]: 'keyword', + [SqlBaseLexer.AS]: 'keyword', + [SqlBaseLexer.ASC]: 'keyword', + [SqlBaseLexer.AT]: 'keyword', + [SqlBaseLexer.AUTHORIZATION]: 'keyword', + [SqlBaseLexer.BERNOULLI]: 'keyword', + [SqlBaseLexer.BETWEEN]: 'keyword', + [SqlBaseLexer.BOTH]: 'keyword', + [SqlBaseLexer.BY]: 'keyword', + [SqlBaseLexer.CALL]: 'keyword', + [SqlBaseLexer.CASCADE]: 'keyword', + [SqlBaseLexer.CASE]: 'keyword', + [SqlBaseLexer.CAST]: 'keyword', + [SqlBaseLexer.CATALOG]: 'keyword', + [SqlBaseLexer.CATALOGS]: 'keyword', + [SqlBaseLexer.COLUMN]: 'keyword', + [SqlBaseLexer.COLUMNS]: 'keyword', + [SqlBaseLexer.COMMENT]: 'keyword', + [SqlBaseLexer.COMMIT]: 'keyword', + [SqlBaseLexer.COMMITTED]: 'keyword', + [SqlBaseLexer.CONDITIONAL]: 'keyword', + [SqlBaseLexer.CONSTRAINT]: 'keyword', + [SqlBaseLexer.COUNT]: 'keyword', + [SqlBaseLexer.COPARTITION]: 'keyword', + [SqlBaseLexer.CREATE]: 'keyword', + [SqlBaseLexer.CROSS]: 'keyword', + [SqlBaseLexer.CUBE]: 'keyword', + [SqlBaseLexer.CURRENT]: 'keyword', + [SqlBaseLexer.CURRENT_CATALOG]: 'keyword', + [SqlBaseLexer.CURRENT_DATE]: 'keyword', + [SqlBaseLexer.CURRENT_PATH]: 'keyword', + [SqlBaseLexer.CURRENT_ROLE]: 'keyword', + [SqlBaseLexer.CURRENT_SCHEMA]: 'keyword', + [SqlBaseLexer.CURRENT_TIME]: 'keyword', + [SqlBaseLexer.CURRENT_TIMESTAMP]: 'keyword', + [SqlBaseLexer.CURRENT_USER]: 'keyword', + [SqlBaseLexer.DATA]: 'keyword', + [SqlBaseLexer.DATE]: 'keyword', + [SqlBaseLexer.DAY]: 'keyword', + [SqlBaseLexer.DEALLOCATE]: 'keyword', + [SqlBaseLexer.DEFAULT]: 'keyword', + [SqlBaseLexer.DEFINE]: 'keyword', + [SqlBaseLexer.DEFINER]: 'keyword', + [SqlBaseLexer.DELETE]: 'keyword', + [SqlBaseLexer.DENY]: 'keyword', + [SqlBaseLexer.DESC]: 'keyword', + [SqlBaseLexer.DESCRIBE]: 'keyword', + [SqlBaseLexer.DESCRIPTOR]: 'keyword', + [SqlBaseLexer.DISTINCT]: 'keyword', + [SqlBaseLexer.DISTRIBUTED]: 'keyword', + [SqlBaseLexer.DOUBLE]: 'keyword', + [SqlBaseLexer.DROP]: 'keyword', + [SqlBaseLexer.ELSE]: 'keyword', + [SqlBaseLexer.EMPTY]: 'keyword', + [SqlBaseLexer.ENCODING]: 'keyword', + [SqlBaseLexer.END]: 'keyword', + [SqlBaseLexer.ERROR]: 'keyword', + [SqlBaseLexer.ESCAPE]: 'keyword', + [SqlBaseLexer.EXCEPT]: 'keyword', + [SqlBaseLexer.EXCLUDING]: 'keyword', + [SqlBaseLexer.EXECUTE]: 'keyword', + [SqlBaseLexer.EXISTS]: 'keyword', + [SqlBaseLexer.EXPLAIN]: 'keyword', + [SqlBaseLexer.EXTRACT]: 'keyword', + [SqlBaseLexer.FALSE]: 'keyword', + [SqlBaseLexer.FETCH]: 'keyword', + [SqlBaseLexer.FILTER]: 'keyword', + [SqlBaseLexer.FINAL]: 'keyword', + [SqlBaseLexer.FIRST]: 'keyword', + [SqlBaseLexer.FOLLOWING]: 'keyword', + [SqlBaseLexer.FOR]: 'keyword', + [SqlBaseLexer.FORMAT]: 'keyword', + [SqlBaseLexer.FROM]: 'keyword', + [SqlBaseLexer.FULL]: 'keyword', + [SqlBaseLexer.FUNCTIONS]: 'keyword', + [SqlBaseLexer.GRACE]: 'keyword', + [SqlBaseLexer.GRANT]: 'keyword', + [SqlBaseLexer.GRANTED]: 'keyword', + [SqlBaseLexer.GRANTS]: 'keyword', + [SqlBaseLexer.GRAPHVIZ]: 'keyword', + [SqlBaseLexer.GROUP]: 'keyword', + [SqlBaseLexer.GROUPING]: 'keyword', + [SqlBaseLexer.GROUPS]: 'keyword', + [SqlBaseLexer.HAVING]: 'keyword', + [SqlBaseLexer.HOUR]: 'keyword', + [SqlBaseLexer.IF]: 'keyword', + [SqlBaseLexer.IGNORE]: 'keyword', + [SqlBaseLexer.IMMEDIATE]: 'keyword', + [SqlBaseLexer.IN]: 'keyword', + [SqlBaseLexer.INCLUDING]: 'keyword', + [SqlBaseLexer.INITIAL]: 'keyword', + [SqlBaseLexer.INNER]: 'keyword', + [SqlBaseLexer.INPUT]: 'keyword', + [SqlBaseLexer.INSERT]: 'keyword', + [SqlBaseLexer.INTERSECT]: 'keyword', + [SqlBaseLexer.INTERVAL]: 'keyword', + [SqlBaseLexer.INTO]: 'keyword', + [SqlBaseLexer.INVOKER]: 'keyword', + [SqlBaseLexer.IO]: 'keyword', + [SqlBaseLexer.IS]: 'keyword', + [SqlBaseLexer.ISOLATION]: 'keyword', + [SqlBaseLexer.JOIN]: 'keyword', + [SqlBaseLexer.JSON]: 'keyword', + [SqlBaseLexer.JSON_ARRAY]: 'keyword', + [SqlBaseLexer.JSON_EXISTS]: 'keyword', + [SqlBaseLexer.JSON_OBJECT]: 'keyword', + [SqlBaseLexer.JSON_QUERY]: 'keyword', + [SqlBaseLexer.JSON_TABLE]: 'keyword', + [SqlBaseLexer.JSON_VALUE]: 'keyword', + [SqlBaseLexer.KEEP]: 'keyword', + [SqlBaseLexer.KEY]: 'keyword', + [SqlBaseLexer.KEYS]: 'keyword', + [SqlBaseLexer.LAST]: 'keyword', + [SqlBaseLexer.LATERAL]: 'keyword', + [SqlBaseLexer.LEADING]: 'keyword', + [SqlBaseLexer.LEFT]: 'keyword', + [SqlBaseLexer.LEVEL]: 'keyword', + [SqlBaseLexer.LIKE]: 'keyword', + [SqlBaseLexer.LIMIT]: 'keyword', + [SqlBaseLexer.LISTAGG]: 'keyword', + [SqlBaseLexer.LOCAL]: 'keyword', + [SqlBaseLexer.LOCALTIME]: 'keyword', + [SqlBaseLexer.LOCALTIMESTAMP]: 'keyword', + [SqlBaseLexer.LOGICAL]: 'keyword', + [SqlBaseLexer.MAP]: 'keyword', + [SqlBaseLexer.MATCH]: 'keyword', + [SqlBaseLexer.MATCHED]: 'keyword', + [SqlBaseLexer.MATCHES]: 'keyword', + [SqlBaseLexer.MATCH_RECOGNIZE]: 'keyword', + [SqlBaseLexer.MATERIALIZED]: 'keyword', + [SqlBaseLexer.MEASURES]: 'keyword', + [SqlBaseLexer.MERGE]: 'keyword', + [SqlBaseLexer.MINUTE]: 'keyword', + [SqlBaseLexer.MONTH]: 'keyword', + [SqlBaseLexer.NATURAL]: 'keyword', + [SqlBaseLexer.NESTED]: 'keyword', + [SqlBaseLexer.NEXT]: 'keyword', + [SqlBaseLexer.NFC]: 'keyword', + [SqlBaseLexer.NFD]: 'keyword', + [SqlBaseLexer.NFKC]: 'keyword', + [SqlBaseLexer.NFKD]: 'keyword', + [SqlBaseLexer.NO]: 'keyword', + [SqlBaseLexer.NONE]: 'keyword', + [SqlBaseLexer.NORMALIZE]: 'keyword', + [SqlBaseLexer.NOT]: 'keyword', + [SqlBaseLexer.NULL]: 'keyword', + [SqlBaseLexer.NULLIF]: 'keyword', + [SqlBaseLexer.NULLS]: 'keyword', + [SqlBaseLexer.OBJECT]: 'keyword', + [SqlBaseLexer.OF]: 'keyword', + [SqlBaseLexer.OFFSET]: 'keyword', + [SqlBaseLexer.OMIT]: 'keyword', + [SqlBaseLexer.ON]: 'keyword', + [SqlBaseLexer.ONE]: 'keyword', + [SqlBaseLexer.ONLY]: 'keyword', + [SqlBaseLexer.OPTION]: 'keyword', + [SqlBaseLexer.OR]: 'keyword', + [SqlBaseLexer.ORDER]: 'keyword', + [SqlBaseLexer.ORDINALITY]: 'keyword', + [SqlBaseLexer.OUTER]: 'keyword', + [SqlBaseLexer.OUTPUT]: 'keyword', + [SqlBaseLexer.OVER]: 'keyword', + [SqlBaseLexer.OVERFLOW]: 'keyword', + [SqlBaseLexer.PARTITION]: 'keyword', + [SqlBaseLexer.PARTITIONS]: 'keyword', + [SqlBaseLexer.PASSING]: 'keyword', + [SqlBaseLexer.PAST]: 'keyword', + [SqlBaseLexer.PATH]: 'keyword', + [SqlBaseLexer.PATTERN]: 'keyword', + [SqlBaseLexer.PER]: 'keyword', + [SqlBaseLexer.PERIOD]: 'keyword', + [SqlBaseLexer.PERMUTE]: 'keyword', + [SqlBaseLexer.PLAN]: 'keyword', + [SqlBaseLexer.POSITION]: 'keyword', + [SqlBaseLexer.PRECEDING]: 'keyword', + [SqlBaseLexer.PRECISION]: 'keyword', + [SqlBaseLexer.PREPARE]: 'keyword', + [SqlBaseLexer.PRIVILEGES]: 'keyword', + [SqlBaseLexer.PROPERTIES]: 'keyword', + [SqlBaseLexer.PRUNE]: 'keyword', + [SqlBaseLexer.QUOTES]: 'keyword', + [SqlBaseLexer.RANGE]: 'keyword', + [SqlBaseLexer.READ]: 'keyword', + [SqlBaseLexer.RECURSIVE]: 'keyword', + [SqlBaseLexer.REFRESH]: 'keyword', + [SqlBaseLexer.RENAME]: 'keyword', + [SqlBaseLexer.REPEATABLE]: 'keyword', + [SqlBaseLexer.REPLACE]: 'keyword', + [SqlBaseLexer.RESET]: 'keyword', + [SqlBaseLexer.RESPECT]: 'keyword', + [SqlBaseLexer.RESTRICT]: 'keyword', + [SqlBaseLexer.RETURNING]: 'keyword', + [SqlBaseLexer.REVOKE]: 'keyword', + [SqlBaseLexer.RIGHT]: 'keyword', + [SqlBaseLexer.ROLE]: 'keyword', + [SqlBaseLexer.ROLES]: 'keyword', + [SqlBaseLexer.ROLLBACK]: 'keyword', + [SqlBaseLexer.ROLLUP]: 'keyword', + [SqlBaseLexer.ROW]: 'keyword', + [SqlBaseLexer.ROWS]: 'keyword', + [SqlBaseLexer.RUNNING]: 'keyword', + [SqlBaseLexer.SCALAR]: 'keyword', + [SqlBaseLexer.SCHEMA]: 'keyword', + [SqlBaseLexer.SCHEMAS]: 'keyword', + [SqlBaseLexer.SECOND]: 'keyword', + [SqlBaseLexer.SECURITY]: 'keyword', + [SqlBaseLexer.SEEK]: 'keyword', + [SqlBaseLexer.SELECT]: 'keyword', + [SqlBaseLexer.SERIALIZABLE]: 'keyword', + [SqlBaseLexer.SESSION]: 'keyword', + [SqlBaseLexer.SET]: 'keyword', + [SqlBaseLexer.SETS]: 'keyword', + [SqlBaseLexer.SHOW]: 'keyword', + [SqlBaseLexer.SOME]: 'keyword', + [SqlBaseLexer.START]: 'keyword', + [SqlBaseLexer.STATS]: 'keyword', + [SqlBaseLexer.SUBSET]: 'keyword', + [SqlBaseLexer.SUBSTRING]: 'keyword', + [SqlBaseLexer.SYSTEM]: 'keyword', + [SqlBaseLexer.TABLE]: 'keyword', + [SqlBaseLexer.TABLES]: 'keyword', + [SqlBaseLexer.TABLESAMPLE]: 'keyword', + [SqlBaseLexer.TEXT]: 'keyword', + [SqlBaseLexer.TEXT_STRING]: 'string', + [SqlBaseLexer.THEN]: 'keyword', + [SqlBaseLexer.TIES]: 'keyword', + [SqlBaseLexer.TIME]: 'keyword', + [SqlBaseLexer.TIMESTAMP]: 'keyword', + [SqlBaseLexer.TO]: 'keyword', + [SqlBaseLexer.TRAILING]: 'keyword', + [SqlBaseLexer.TRANSACTION]: 'keyword', + [SqlBaseLexer.TRIM]: 'keyword', + [SqlBaseLexer.TRUE]: 'keyword', + [SqlBaseLexer.TRUNCATE]: 'keyword', + [SqlBaseLexer.TRY_CAST]: 'keyword', + [SqlBaseLexer.TYPE]: 'keyword', + [SqlBaseLexer.UESCAPE]: 'keyword', + [SqlBaseLexer.UNBOUNDED]: 'keyword', + [SqlBaseLexer.UNCOMMITTED]: 'keyword', + [SqlBaseLexer.UNCONDITIONAL]: 'keyword', + [SqlBaseLexer.UNION]: 'keyword', + [SqlBaseLexer.UNIQUE]: 'keyword', + [SqlBaseLexer.UNKNOWN]: 'keyword', + [SqlBaseLexer.UNMATCHED]: 'keyword', + [SqlBaseLexer.UNNEST]: 'keyword', + [SqlBaseLexer.UPDATE]: 'keyword', + [SqlBaseLexer.USE]: 'keyword', + [SqlBaseLexer.USER]: 'keyword', + [SqlBaseLexer.USING]: 'keyword', + [SqlBaseLexer.UTF16]: 'keyword', + [SqlBaseLexer.UTF32]: 'keyword', + [SqlBaseLexer.UTF8]: 'keyword', + [SqlBaseLexer.VALIDATE]: 'keyword', + [SqlBaseLexer.VALUE]: 'keyword', + [SqlBaseLexer.VALUES]: 'keyword', + [SqlBaseLexer.VERBOSE]: 'keyword', + [SqlBaseLexer.VERSION]: 'keyword', + [SqlBaseLexer.VIEW]: 'keyword', + [SqlBaseLexer.WHEN]: 'keyword', + [SqlBaseLexer.WHERE]: 'keyword', + [SqlBaseLexer.WINDOW]: 'keyword', + [SqlBaseLexer.WITH]: 'keyword', + [SqlBaseLexer.WITHIN]: 'keyword', + [SqlBaseLexer.WITHOUT]: 'keyword', + [SqlBaseLexer.WORK]: 'keyword', + [SqlBaseLexer.WRAPPER]: 'keyword', + [SqlBaseLexer.WRITE]: 'keyword', + [SqlBaseLexer.YEAR]: 'keyword', + [SqlBaseLexer.ZONE]: 'keyword', + [SqlBaseLexer.EQ]: 'operator', + [SqlBaseLexer.NEQ]: 'operator', + [SqlBaseLexer.LT]: 'operator', + [SqlBaseLexer.LTE]: 'operator', + [SqlBaseLexer.GT]: 'operator', + [SqlBaseLexer.GTE]: 'operator', + [SqlBaseLexer.PLUS]: 'operator', + [SqlBaseLexer.MINUS]: 'operator', + [SqlBaseLexer.ASTERISK]: 'operator', + [SqlBaseLexer.SLASH]: 'operator', + [SqlBaseLexer.PERCENT]: 'operator', + [SqlBaseLexer.CONCAT]: 'operator', + [SqlBaseLexer.QUESTION_MARK]: 'operator', + [SqlBaseLexer.STRING]: 'string', + [SqlBaseLexer.UNICODE_STRING]: 'string', + [SqlBaseLexer.BINARY_LITERAL]: 'string', + [SqlBaseLexer.INTEGER_VALUE]: 'number', + [SqlBaseLexer.DECIMAL_VALUE]: 'number', + [SqlBaseLexer.DOUBLE_VALUE]: 'number', + [SqlBaseLexer.IDENTIFIER]: 'identifier', + [SqlBaseLexer.DIGIT_IDENTIFIER]: 'identifier', + [SqlBaseLexer.QUOTED_IDENTIFIER]: 'identifier', + [SqlBaseLexer.BACKQUOTED_IDENTIFIER]: 'identifier', + [SqlBaseLexer.SIMPLE_COMMENT]: 'comment', + [SqlBaseLexer.BRACKETED_COMMENT]: 'comment', + [SqlBaseLexer.WS]: 'whitespace', + [SqlBaseLexer.UNRECOGNIZED]: 'invalid', +} diff --git a/precise/src/utils/ClearButton.tsx b/precise/src/utils/ClearButton.tsx index b3b8458..89031ca 100644 --- a/precise/src/utils/ClearButton.tsx +++ b/precise/src/utils/ClearButton.tsx @@ -1,42 +1,42 @@ -import React, { useState } from 'react'; -import { Trash2, AlertTriangle } from 'lucide-react'; +import React, { useState } from 'react' +import { Trash2, AlertTriangle } from 'lucide-react' interface ClearButtonProps { - onClear: () => void; + onClear: () => void } const ClearButton: React.FC = ({ onClear }) => { - const [confirming, setConfirming] = useState(false); + const [confirming, setConfirming] = useState(false) - const handleClear = () => { - if (!confirming) { - setConfirming(true); - setTimeout(() => setConfirming(false), 2000); - } else { - onClear(); - setConfirming(false); + const handleClear = () => { + if (!confirming) { + setConfirming(true) + setTimeout(() => setConfirming(false), 2000) + } else { + onClear() + setConfirming(false) + } } - }; - return ( -
- {confirming ? ( - <> - - Confirm - - ) : ( - <> - - Clear - - )} -
- ); -}; + return ( +
+ {confirming ? ( + <> + + Confirm + + ) : ( + <> + + Clear + + )} +
+ ) +} -export default ClearButton; \ No newline at end of file +export default ClearButton diff --git a/precise/src/utils/CopyLink.tsx b/precise/src/utils/CopyLink.tsx index 1dac085..790032a 100644 --- a/precise/src/utils/CopyLink.tsx +++ b/precise/src/utils/CopyLink.tsx @@ -1,38 +1,38 @@ -import React, { useState } from 'react'; -import { Copy, CheckCircle } from 'lucide-react'; +import React, { useState } from 'react' +import { Copy, CheckCircle } from 'lucide-react' interface CopyLinkProps { - copy: () => void; + copy: () => void } const CopyLink: React.FC = ({ copy }) => { - const [copied, setCopied] = useState(false); + const [copied, setCopied] = useState(false) - const handleCopy = () => { - copy(); - setCopied(true); - setTimeout(() => setCopied(false), 1500); - }; + const handleCopy = () => { + copy() + setCopied(true) + setTimeout(() => setCopied(false), 1500) + } - return ( -
- {copied ? ( - <> - - Copied! - - ) : ( - <> - - Copy - - )} -
- ); -}; + return ( +
+ {copied ? ( + <> + + Copied! + + ) : ( + <> + + Copy + + )} +
+ ) +} -export default CopyLink; \ No newline at end of file +export default CopyLink diff --git a/precise/src/utils/ErrorBoxProvider.tsx b/precise/src/utils/ErrorBoxProvider.tsx index deb6dab..5d7bdca 100644 --- a/precise/src/utils/ErrorBoxProvider.tsx +++ b/precise/src/utils/ErrorBoxProvider.tsx @@ -1,48 +1,41 @@ -import React, { useState } from 'react'; -import CloseIcon from '../assets/close.png'; -import './errorbox.css'; +import React, { useState } from 'react' +import CloseIcon from '../assets/close.png' +import './errorbox.css' interface ErrorBoxProviderProps { - errorMessage: string; - errorContext: string; + errorMessage: string + errorContext: string } const ErrorBox: React.FC = ({ errorMessage, errorContext }) => { - const [isVisible, setIsVisible] = useState(true); - const [errorTimestamp] = useState(new Date().toISOString()); // Capture timestamp when error occurs + const [isVisible, setIsVisible] = useState(true) + const [errorTimestamp] = useState(new Date().toISOString()) // Capture timestamp when error occurs if (!errorMessage || !isVisible) { - return null; + return null } return (
Error: {errorContext} -
setIsVisible(false)} role="button" aria-label="Close error message" > - Close + Close
{errorMessage} -
- {errorTimestamp} -
+
{errorTimestamp}
- ); -}; + ) +} -export default ErrorBox; \ No newline at end of file +export default ErrorBox diff --git a/precise/src/utils/ProgressBar.tsx b/precise/src/utils/ProgressBar.tsx index 8535457..9a7cada 100644 --- a/precise/src/utils/ProgressBar.tsx +++ b/precise/src/utils/ProgressBar.tsx @@ -1,35 +1,33 @@ -import * as React from 'react'; +import * as React from 'react' // ProgressBar properties interface ProgressBarProps { - progress: number; - state: string; + progress: number + state: string } // ProgressBar state -interface ProgressBarState { -} +interface ProgressBarState {} export default class ProgressBar extends React.Component { - constructor(props: ProgressBarProps) { - super(props); + super(props) this.state = { - progress: 0 + progress: 0, } } render() { return (
- {this.props.progress === 0 && Number.isFinite(this.props.progress) ? + {this.props.progress === 0 && Number.isFinite(this.props.progress) ? (
{this.props.state}
- : -
- {Math.round(this.props.progress) + "%"} + ) : ( +
+ {Math.round(this.props.progress) + '%'}
- } + )}
- ); + ) } -} \ No newline at end of file +} diff --git a/precise/src/utils/ResizableContainer.tsx b/precise/src/utils/ResizableContainer.tsx index 2b1e71e..4268c8a 100644 --- a/precise/src/utils/ResizableContainer.tsx +++ b/precise/src/utils/ResizableContainer.tsx @@ -1,83 +1,83 @@ -import React, { useState, useRef, useEffect } from 'react'; +import React, { useState, useRef, useEffect } from 'react' interface ResizableContainerProps { - children: React.ReactNode; - initialHeight: string; - minHeight?: string; - maxHeight?: string; - onHeightChange: (newHeight: string) => void; + children: React.ReactNode + initialHeight: string + minHeight?: string + maxHeight?: string + onHeightChange: (newHeight: string) => void } -const ResizableContainer: React.FC = ({ - children, - initialHeight, - minHeight = '100px', - maxHeight = '80vh', - onHeightChange +const ResizableContainer: React.FC = ({ + children, + initialHeight, + minHeight = '100px', + maxHeight = '80vh', + onHeightChange, }) => { - const [height, setHeight] = useState(initialHeight); - const containerRef = useRef(null); - const resizeHandleRef = useRef(null); + const [height, setHeight] = useState(initialHeight) + const containerRef = useRef(null) + const resizeHandleRef = useRef(null) - useEffect(() => { - const container = containerRef.current; - const resizeHandle = resizeHandleRef.current; - let isResizing = false; - let startY: number; - let startHeight: number; + useEffect(() => { + const container = containerRef.current + const resizeHandle = resizeHandleRef.current + let isResizing = false + let startY: number + let startHeight: number - const onMouseDown = (e: MouseEvent) => { - isResizing = true; - startY = e.clientY; - startHeight = container!.getBoundingClientRect().height; - document.addEventListener('mousemove', onMouseMove); - document.addEventListener('mouseup', onMouseUp); - }; + const onMouseDown = (e: MouseEvent) => { + isResizing = true + startY = e.clientY + startHeight = container!.getBoundingClientRect().height + document.addEventListener('mousemove', onMouseMove) + document.addEventListener('mouseup', onMouseUp) + } - const onMouseMove = (e: MouseEvent) => { - if (!isResizing) return; - const diff = e.clientY - startY; - const newHeight = startHeight + diff; - const minHeightPx = parseInt(minHeight); - const maxHeightPx = parseInt(maxHeight); - const clampedHeight = Math.max(minHeightPx, Math.min(maxHeightPx, newHeight)); - const newHeightString = `${clampedHeight}px`; - setHeight(newHeightString); - onHeightChange(newHeightString); - }; + const onMouseMove = (e: MouseEvent) => { + if (!isResizing) return + const diff = e.clientY - startY + const newHeight = startHeight + diff + const minHeightPx = parseInt(minHeight) + const maxHeightPx = parseInt(maxHeight) + const clampedHeight = Math.max(minHeightPx, Math.min(maxHeightPx, newHeight)) + const newHeightString = `${clampedHeight}px` + setHeight(newHeightString) + onHeightChange(newHeightString) + } - const onMouseUp = () => { - isResizing = false; - document.removeEventListener('mousemove', onMouseMove); - document.removeEventListener('mouseup', onMouseUp); - }; + const onMouseUp = () => { + isResizing = false + document.removeEventListener('mousemove', onMouseMove) + document.removeEventListener('mouseup', onMouseUp) + } - resizeHandle?.addEventListener('mousedown', onMouseDown); + resizeHandle?.addEventListener('mousedown', onMouseDown) - return () => { - resizeHandle?.removeEventListener('mousedown', onMouseDown); - document.removeEventListener('mousemove', onMouseMove); - document.removeEventListener('mouseup', onMouseUp); - }; - }, [minHeight, maxHeight, onHeightChange]); + return () => { + resizeHandle?.removeEventListener('mousedown', onMouseDown) + document.removeEventListener('mousemove', onMouseMove) + document.removeEventListener('mouseup', onMouseUp) + } + }, [minHeight, maxHeight, onHeightChange]) - return ( -
- {children} -
-
- ); -}; + return ( +
+ {children} +
+
+ ) +} -export default ResizableContainer; \ No newline at end of file +export default ResizableContainer