Repository navigation
perf(plugins): list every schema's tables in one query on Oracle, Dameng, HANA, Trino, CockroachDB and Redshift #3708
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
976fe0f
0f0c62b
7375ad5
d10f1d0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| import TableProTrinoCore | ||
| import Testing | ||
|
|
||
| struct TrinoIntrospectionSQLTests { | ||
| @Test("A schema set is listed in one query that names each schema and projects it") | ||
| func schemaSetListing() { | ||
| let statement = TrinoIntrospectionSQL.listTables(catalog: "hive", inSchemas: ["sales", "o'brien"]) | ||
| #expect(statement.contains("FROM \"hive\".information_schema.tables")) | ||
| #expect(statement.contains("WHERE table_schema IN ('sales', 'o''brien')")) | ||
| #expect(statement.contains("SELECT table_name, table_type, table_schema")) | ||
| #expect(TrinoIntrospectionSQL.listTables(catalog: "hive", inSchemas: []).contains("WHERE false")) | ||
| } | ||
|
|
||
| @Test("A schema set's materialized views come from one query that projects the schema") | ||
| func schemaSetMaterializedViews() { | ||
| let statement = TrinoIntrospectionSQL.listMaterializedViews( | ||
| catalog: "iceberg", inSchemas: ["a", "b"], flavor: .trino | ||
| ) | ||
| #expect(statement.contains("SELECT name, schema_name FROM system.metadata.materialized_views")) | ||
| #expect(statement.contains("catalog_name = 'iceberg' AND schema_name IN ('a', 'b')")) | ||
| } | ||
|
|
||
| @Test("On Presto a schema set's materialized views come from information_schema in one query") | ||
| func prestoSchemaSetMaterializedViews() { | ||
| let statement = TrinoIntrospectionSQL.listMaterializedViews( | ||
| catalog: "hive", inSchemas: ["a", "o'brien"], flavor: .presto | ||
| ) | ||
| #expect(statement.contains(#"SELECT table_name, table_schema FROM "hive".information_schema.materialized_views"#)) | ||
| #expect(statement.contains("WHERE table_schema IN ('a', 'o''brien')")) | ||
| } | ||
|
|
||
| @Test("A quote that a combining mark follows is still doubled") | ||
| func combiningMarkCannotHideAQuote() { | ||
| #expect(TrinoIntrospectionSQL.quoteLiteral("x'\u{301} OR true--") == "'x''\u{301} OR true--'") | ||
| #expect(TrinoIntrospectionSQL.quoteIdentifier("a\"\u{301}") == "\"a\"\"\u{301}\"") | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -22,6 +22,21 @@ enum HanaCatalogQueries { | |
| """ | ||
| } | ||
|
|
||
| static func tables(inSchemas schemas: [String]) -> String { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| let owners = schemas.map(HanaSQL.quoteLiteral).joined(separator: ", ") | ||
| return """ | ||
| SELECT TABLE_NAME, 'TABLE' AS OBJECT_TYPE, COMMENTS, SCHEMA_NAME | ||
| FROM SYS.TABLES | ||
| WHERE SCHEMA_NAME IN (\(owners)) | ||
| AND IS_USER_DEFINED_TYPE = 'FALSE' | ||
| UNION ALL | ||
| SELECT VIEW_NAME, 'VIEW', COMMENTS, SCHEMA_NAME | ||
| FROM SYS.VIEWS | ||
| WHERE SCHEMA_NAME IN (\(owners)) | ||
| ORDER BY 4, 1 | ||
| """ | ||
| } | ||
|
|
||
| static func columns(schema: String, table: String?) -> String { | ||
| let owner = HanaSQL.quoteLiteral(schema) | ||
| let tableFilter = table.map { "\n AND TABLE_NAME = \(HanaSQL.quoteLiteral($0))" } ?? "" | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -487,14 +487,26 @@ final class OraclePluginDriver: PluginDatabaseDriver, @unchecked Sendable { | |
|
|
||
| func fetchTables(schema: String?) async throws -> [PluginTableInfo] { | ||
| let result = try await rawQuery(OracleSchemaQueries.tables(schema: effectiveSchema(schema))) | ||
| return result.rows.compactMap(OracleSchemaQueries.parseTableRow).map { row in | ||
| PluginTableInfo( | ||
| name: row.name, | ||
| type: row.isView ? "VIEW" : (row.isPartitioned ? "PARTITIONED TABLE" : "TABLE"), | ||
| comment: nil, | ||
| partitionCount: row.partitionCount | ||
| ) | ||
| return result.rows.compactMap(OracleSchemaQueries.parseTableRow).map(Self.tableInfo) | ||
| } | ||
|
|
||
| func fetchTables(inSchemas schemas: [String]) async throws -> [PluginTableInfo]? { | ||
| var tables: [PluginTableInfo] = [] | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Chunks run sequentially and the result is only ordered within each 1,000-owner chunk, not globally by owner/name as the query's ORDER BY implies. |
||
| for query in OracleSchemaQueries.tables(inSchemas: schemas) { | ||
| let result = try await rawQuery(query) | ||
| tables += result.rows.compactMap(OracleSchemaQueries.parseTableRow).map(Self.tableInfo) | ||
| } | ||
| return tables | ||
| } | ||
|
|
||
| private static func tableInfo(_ row: OracleTableRow) -> PluginTableInfo { | ||
| PluginTableInfo( | ||
| name: row.name, | ||
| type: row.isView ? "VIEW" : (row.isPartitioned ? "PARTITIONED TABLE" : "TABLE"), | ||
| schema: row.schema, | ||
| comment: nil, | ||
| partitionCount: row.partitionCount | ||
| ) | ||
| } | ||
|
|
||
| func fetchColumns(table: String, schema: String?) async throws -> [PluginColumnInfo] { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -17,6 +17,30 @@ import TableProPluginKit | |
| /// `schema` is non-optional on purpose: the `?? currentSchema` fallback stays at the call site, | ||
| /// where the driver knows its own session, so no caller can reach these builders without one. | ||
| public enum CockroachRelationSQL { | ||
| public static func tablesQuery(schema: String) -> String { | ||
| """ | ||
| SELECT table_name, table_type | ||
| FROM information_schema.tables | ||
| WHERE table_schema = \(PostgreSQLObjectQueries.quoteLiteral(schema)) | ||
| ORDER BY table_name | ||
| """ | ||
| } | ||
|
|
||
| public static func tablesQuery(inSchemas schemas: [String]) -> String { | ||
| """ | ||
| SELECT table_name, table_type, table_schema | ||
| FROM information_schema.tables | ||
| WHERE \(PostgreSQLObjectQueries.membership("table_schema", in: schemas)) | ||
| ORDER BY table_schema, table_name | ||
| """ | ||
| } | ||
|
|
||
| public static func table(fromRow row: [String?]) -> PluginTableInfo? { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| guard let name = row[safe: 0] ?? nil else { return nil } | ||
| let listedType = ((row[safe: 1] ?? nil) ?? "BASE TABLE").uppercased() | ||
| return PluginTableInfo(name: name, type: listedType.contains("VIEW") ? "VIEW" : "TABLE", schema: row[safe: 2] ?? nil) | ||
| } | ||
|
|
||
| public static func showCreateTable(table: String, schema: String) -> String { | ||
| "SHOW CREATE TABLE \(PostgreSQLObjectQueries.qualifiedName(schema: schema, name: table))" | ||
| } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hard-coded
chunkSize = 1_000with each owner bound twice means 2,000 bind parameters per statement, unverified against DM's cap. A failure falls back to per-schema queries, losing the gain on large catalogs. Also duplicatesOracleSchemaQueries.maxOwnersPerTableListing.