Skip to content

Commit a35bf9d

Browse files
authored
feat(presto-client): add get catalog, schema, table & column utility methods (#18)
1 parent 8221bdc commit a35bf9d

File tree

5 files changed

+280
-16
lines changed

5 files changed

+280
-16
lines changed

apps/nest-server/src/app/app.controller.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,29 @@
1-
import { Controller, Get } from '@nestjs/common'
1+
import { Controller, Get, Param } from '@nestjs/common'
22

33
import { AppService } from './app.service'
44

55
@Controller()
66
export class AppController {
77
constructor(private readonly appService: AppService) {}
88

9+
@Get('get-schemas')
10+
async getSchemas(@Param('catalog') catalog: string) {
11+
try {
12+
return await this.appService.getSchemas(catalog)
13+
} catch (err) {
14+
console.error(err)
15+
}
16+
}
17+
18+
@Get('get-catalogs')
19+
async getCatalogs() {
20+
try {
21+
return await this.appService.getCatalogs()
22+
} catch (err) {
23+
console.error(err)
24+
}
25+
}
26+
927
@Get('query-test')
1028
async getData() {
1129
try {

apps/nest-server/src/app/app.service.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,38 @@ import PrestoClient, { PrestoClientConfig, PrestoError } from '@prestodb/presto-
33

44
@Injectable()
55
export class AppService {
6+
async getCatalogs(): Promise<string[] | undefined> {
7+
const clientParams: PrestoClientConfig = {
8+
catalog: 'tpch',
9+
host: 'http://localhost',
10+
port: 8080,
11+
schema: 'sf1',
12+
user: 'root',
13+
}
14+
const client = new PrestoClient(clientParams)
15+
try {
16+
return await client.getCatalogs()
17+
} catch (error) {
18+
console.error(error)
19+
}
20+
}
21+
22+
async getSchemas(catalog: string): Promise<string[] | undefined> {
23+
const clientParams: PrestoClientConfig = {
24+
catalog: 'tpch',
25+
host: 'http://localhost',
26+
port: 8080,
27+
schema: 'sf1',
28+
user: 'root',
29+
}
30+
const client = new PrestoClient(clientParams)
31+
try {
32+
return await client.getSchemas(catalog)
33+
} catch (error) {
34+
console.error(error)
35+
}
36+
}
37+
638
async getData(): Promise<unknown> {
739
const clientParams: PrestoClientConfig = {
840
catalog: 'tpch',

presto-client/README.md

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,3 +99,68 @@ Additional notes
9999
- The `query` method is asynchronous and will return a promise that resolves to a PrestoQuery object.
100100
- The `query` method will automatically retry the query if it fails due to a transient error.
101101
- The `query` method will cancel the query if the client is destroyed.
102+
103+
## Query catalog, schema, table and column metadata
104+
105+
### Get Catalogs
106+
107+
The `getCatalogs` method retrieves all available database catalogs, returning them as an array of strings.
108+
109+
#### Example usage
110+
111+
```typescript
112+
const catalogs = await prestoClient.getCatalogs()
113+
console.log(catalogs)
114+
```
115+
116+
### Get Schemas
117+
118+
The `getSchemas` method retrieves all schemas within a given catalog. It accepts a catalog parameter, which is a string representing the name of the catalog.
119+
120+
Parameters
121+
122+
- `catalog`: The name of the catalog for which to retrieve schemas.
123+
124+
#### Example usage
125+
126+
```typescript
127+
const schemas = await prestoClient.getSchemas('tpch')
128+
console.log(schemas)
129+
```
130+
131+
### Get Tables
132+
133+
The `getTables` method retrieves a list of tables (of type `Table`) filtered by the given catalog and, optionally, the schema. It accepts an object containing `catalog` and optional `schema` parameters.
134+
135+
Parameters
136+
137+
- `catalog`: The catalog name.
138+
- `schema` (optional): The schema name.
139+
140+
#### Example usage
141+
142+
```typescript
143+
const tables: Table[] = await prestoClient.getTables({ catalog: 'tpch', schema: 'sf100' })
144+
console.log(tables)
145+
```
146+
147+
### Get Columns
148+
149+
The `getColumns` method retrieves a list of columns (of type `Column`) filtered for the given catalog and optional schema and table filters. It accepts an object with `catalog`, and optional `schema` and `table` parameters.
150+
151+
Parameters
152+
153+
- `catalog`: The catalog name.
154+
- `schema` (optional): The schema name.
155+
- `table` (optional): The table name.
156+
157+
#### Example usage
158+
159+
```typescript
160+
const columns: Column[] = await prestoClient.getColumns({
161+
catalog: 'tpch',
162+
schema: 'sf100',
163+
table: 'orders',
164+
})
165+
console.log(columns)
166+
```

presto-client/src/client.ts

Lines changed: 145 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { PrestoClientConfig, PrestoError, PrestoQuery, PrestoResponse } from './client.types'
2+
import { Column, Table } from './information-schema.types'
23

34
export class PrestoClient {
45
private baseUrl: string
@@ -11,6 +12,18 @@ export class PrestoClient {
1112
private timezone?: string
1213
private user: string
1314

15+
/**
16+
* Creates an instance of PrestoClient.
17+
* @param {PrestoClientConfig} config - Configuration object for the PrestoClient.
18+
* @param {string} config.catalog - The default catalog to be used.
19+
* @param {string} config.host - The host address of the Presto server.
20+
* @param {number} config.interval - The polling interval in milliseconds for query status checks.
21+
* @param {number} config.port - The port number on which the Presto server is listening.
22+
* @param {string} [config.schema] - The default schema to be used. Optional.
23+
* @param {string} [config.source] - The name of the source making the query. Optional.
24+
* @param {string} [config.timezone] - The timezone to be used for the session. Optional.
25+
* @param {string} config.user - The username to be used for the Presto session.
26+
*/
1427
constructor({ catalog, host, interval, port, schema, source, timezone, user }: PrestoClientConfig) {
1528
this.baseUrl = `${host || 'http://localhost'}:${port || 8080}/v1/statement`
1629
this.catalog = catalog
@@ -36,25 +49,114 @@ export class PrestoClient {
3649
// TODO: Set up auth
3750
}
3851

39-
private request({
40-
body,
41-
headers,
42-
method,
43-
url,
52+
/**
53+
* Retrieves all catalogs.
54+
* @returns {Promise<string[] | undefined>} An array of all the catalog names.
55+
*/
56+
async getCatalogs(): Promise<string[] | undefined> {
57+
return (await this.query('SHOW CATALOGS')).data?.map(([catalog]) => catalog as string)
58+
}
59+
60+
/**
61+
* Retrieves a list of columns filtered for the given catalog and optional filters.
62+
* @param {Object} options - The options for retrieving columns.
63+
* @param {string} options.catalog - The catalog name.
64+
* @param {string} [options.schema] - The schema name. Optional.
65+
* @param {string} [options.table] - The table name. Optional.
66+
* @returns {Promise<Column[] | undefined>} An array of all the columns that match the given filters.
67+
*/
68+
async getColumns({
69+
catalog,
70+
schema,
71+
table,
4472
}: {
45-
body?: string
46-
headers?: Record<string, string>
47-
method: string
48-
url: string
49-
}) {
50-
return fetch(url, {
51-
body,
52-
headers,
53-
method,
54-
})
73+
catalog: string
74+
schema?: string
75+
table?: string
76+
}): Promise<Column[] | undefined> {
77+
const whereCondition = this.getWhereCondition([
78+
{ key: 'table_schema', value: schema },
79+
{ key: 'table_name', value: table },
80+
])
81+
82+
// The order of the select expression columns is important since we destructure them in the same order below
83+
const query = `SELECT table_catalog, table_schema, table_name, column_name, column_default, is_nullable, data_type, comment, extra_info FROM information_schema.columns ${whereCondition}`
84+
const rawResult = (
85+
await this.query(query, {
86+
catalog,
87+
})
88+
).data
89+
return rawResult?.map(
90+
([
91+
// This destructuring names the fields properly for each row, and converts them to camelCase
92+
tableCatalog,
93+
tableSchema,
94+
tableName,
95+
columnName,
96+
columnDefault,
97+
isNullable,
98+
dataType,
99+
comment,
100+
extraInfo,
101+
]) => ({
102+
tableCatalog,
103+
tableSchema,
104+
tableName,
105+
columnName,
106+
columnDefault,
107+
isNullable,
108+
dataType,
109+
comment,
110+
extraInfo,
111+
}),
112+
) as Column[]
55113
}
56114

57115
/**
116+
* Retrieves all schemas within a given catalog.
117+
* @param {string} catalog - The name of the catalog for which to retrieve schemas.
118+
* @returns {Promise<string[] | undefined>} An array of schema names within the specified catalog.
119+
*/
120+
async getSchemas(catalog: string): Promise<string[] | undefined> {
121+
return (
122+
await this.query('SHOW SCHEMAS', {
123+
catalog,
124+
})
125+
).data?.map(([schema]) => schema as string)
126+
}
127+
128+
/**
129+
* Retrieves a list of tables filtered by the given catalog and optional schema.
130+
* @param {Object} options - The options for retrieving tables.
131+
* @param {string} options.catalog - The catalog name.
132+
* @param {string} [options.schema] - The schema name. Optional.
133+
* @returns {Promise<Table[] | undefined>} An array of tables that match the given filters.
134+
*/
135+
async getTables({ catalog, schema }: { catalog: string; schema?: string }): Promise<Table[] | undefined> {
136+
const whereCondition = this.getWhereCondition([{ key: 'table_schema', value: schema }])
137+
// The order of the select expression columns is important since we destructure them in the same order below
138+
const query = `SELECT table_catalog, table_schema, table_name, table_type FROM information_schema.tables ${whereCondition}`
139+
const rawResult = (
140+
await this.query(query, {
141+
catalog,
142+
})
143+
).data
144+
// This destructuring names the fields properly for each row, and converts them to camelCase
145+
return rawResult?.map(([tableCatalog, tableSchema, tableName, tableType]) => ({
146+
tableCatalog,
147+
tableSchema,
148+
tableName,
149+
tableType,
150+
})) as Table[]
151+
}
152+
153+
/**
154+
* Executes a given query with optional catalog and schema settings.
155+
* @param {string} query - The SQL query string to be executed.
156+
* @param {Object} [options] - Optional parameters for the query.
157+
* @param {string} [options.catalog] - The catalog to be used for the query. Optional.
158+
* @param {string} [options.schema] - The schema to be used for the query. Optional.
159+
* @returns {Promise<PrestoQuery>} A promise that resolves to the result of the query execution.
58160
* @throws {PrestoError} If the underlying Presto engine returns an error
59161
*/
60162
async query(
@@ -143,6 +245,34 @@ export class PrestoClient {
143245
private delay(milliseconds: number) {
144246
return new Promise(resolve => setTimeout(resolve, milliseconds))
145247
}
248+
249+
// This builds a WHERE statement if one or more of the conditions contain non-undefined values
250+
// Currently only works for string values (need more conditions for number and boolean)
251+
private getWhereCondition(conditions: { key: string; value?: string }[]): string {
252+
const filteredConditions = conditions.filter(({ value }) => Boolean(value))
253+
if (filteredConditions.length) {
254+
return `WHERE ${filteredConditions.map(({ key, value }) => `${key} = '${value}'`).join(' AND ')}`
255+
}
256+
return ''
257+
}
258+
259+
private request({
260+
body,
261+
headers,
262+
method,
263+
url,
264+
}: {
265+
body?: string
266+
headers?: Record<string, string>
267+
method: string
268+
url: string
269+
}) {
270+
return fetch(url, {
271+
body,
272+
headers,
273+
method,
274+
})
275+
}
146276
}
147277

148278
export default PrestoClient
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
export interface Table {
2+
tableCatalog: string
3+
tableName: string
4+
tableSchema: string
5+
tableType: string
6+
}
7+
8+
export interface Column {
9+
columnDefault: unknown
10+
columnName: string
11+
comment: string
12+
dataType: string
13+
extraInfo: unknown
14+
isNullable: boolean
15+
ordinalPosition: number
16+
tableCatalog: string
17+
tableName: string
18+
tableSchema: string
19+
}

0 commit comments

Comments
 (0)