-
Notifications
You must be signed in to change notification settings - Fork 0
feat: Add database schema introspection for existing databases #65
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
Conversation
- Added introspectSchema method to Driver interface - Implemented introspection in SqlDriver for PostgreSQL, MySQL, and SQLite - Added convertIntrospectedSchemaToObjects utility to map DB types to ObjectQL - Added introspectAndRegister method to ObjectQL class - Comprehensive tests for introspection and conversion - Documentation and examples Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
- Fix SQL injection risk in SQLite PRAGMA queries by sanitizing table names - Use null coalescing operator for cleaner default value check - Import utility function at top level instead of dynamic import - Add input sanitization for table names in introspection Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
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.
Pull request overview
This PR introduces automatic database schema introspection capabilities, enabling ObjectQL to connect to existing SQL databases without requiring predefined metadata. The system automatically discovers tables, columns, data types, and foreign key relationships from PostgreSQL, MySQL, and SQLite databases.
Changes:
- Added introspection type definitions and interfaces to the core type system
- Implemented database-specific introspection logic for PostgreSQL, MySQL, and SQLite
- Created utility functions to convert introspected schemas into ObjectQL configurations
- Added comprehensive test coverage for introspection functionality
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/foundation/types/src/driver.ts | Defines interfaces for introspected schema metadata and extends Driver with optional introspection method |
| packages/foundation/core/src/util.ts | Implements database type mapping and schema conversion utilities |
| packages/foundation/core/src/app.ts | Adds introspectAndRegister method to ObjectQL class for automatic schema registration |
| packages/drivers/sql/src/index.ts | Implements introspectSchema with database-specific queries for PostgreSQL, MySQL, and SQLite |
| packages/foundation/core/test/introspection.test.ts | Comprehensive unit tests for schema conversion logic |
| packages/drivers/sql/test/introspection.test.ts | Integration tests for SQLite introspection functionality |
| examples/connect-existing-database.ts | Complete working example demonstrating introspection usage |
| docs/schema-introspection.md | Detailed documentation covering features, API, and use cases |
| } | ||
|
|
||
| // Add max length for text fields | ||
| // Add max length for text fields |
Copilot
AI
Jan 14, 2026
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.
Duplicate comment on lines 126 and 127. Remove the duplicate comment to improve code clarity.
| // Add max length for text fields |
packages/drivers/sql/src/index.ts
Outdated
| // SQLite PRAGMA doesn't support parameter binding, so we need to ensure safe identifier | ||
| // Table names in ObjectQL are validated and should be safe, but we add extra protection | ||
| const safeTableName = tableName.replace(/[^a-zA-Z0-9_]/g, ''); | ||
| const result = await this.knex.raw(`PRAGMA foreign_key_list(${safeTableName})`); | ||
|
|
||
| for (const row of result) { |
Copilot
AI
Jan 14, 2026
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.
While regex sanitization provides some protection, constructing raw SQL with string interpolation for SQLite PRAGMA commands still poses injection risks. Consider validating that the sanitized table name actually exists in the database before executing the query, or use a whitelist of known table names from the initial table discovery.
| // SQLite PRAGMA doesn't support parameter binding, so we need to ensure safe identifier | |
| // Table names in ObjectQL are validated and should be safe, but we add extra protection | |
| const safeTableName = tableName.replace(/[^a-zA-Z0-9_]/g, ''); | |
| const result = await this.knex.raw(`PRAGMA foreign_key_list(${safeTableName})`); | |
| for (const row of result) { | |
| // SQLite PRAGMA doesn't support parameter binding, so we need to ensure safe identifier. | |
| // First, verify that the requested table actually exists using a parameterized query. | |
| const tableExistsResult = await this.knex.raw( | |
| "SELECT name FROM sqlite_master WHERE type = 'table' AND name = ?", | |
| [tableName] | |
| ); | |
| const tableExistsRows = Array.isArray(tableExistsResult) | |
| ? tableExistsResult | |
| : (tableExistsResult && (tableExistsResult as any).rows) || []; | |
| if (!Array.isArray(tableExistsRows) || tableExistsRows.length === 0) { | |
| // If the table does not exist, there are no foreign keys to introspect. | |
| return foreignKeys; | |
| } | |
| // SQLite PRAGMA doesn't support parameter binding, so we need to ensure safe identifier. | |
| // Table names in ObjectQL are validated and should be safe, but we add extra protection. | |
| const safeTableName = tableName.replace(/[^a-zA-Z0-9_]/g, ''); | |
| const result = await this.knex.raw(`PRAGMA foreign_key_list(${safeTableName})`); | |
| const pragmaRows = Array.isArray(result) | |
| ? result | |
| : (result && (result as any).rows) || []; | |
| for (const row of pragmaRows) { |
| } else if (this.config.client === 'sqlite3') { | ||
| // SQLite PRAGMA doesn't support parameter binding, so we need to ensure safe identifier | ||
| // Table names in ObjectQL are validated and should be safe, but we add extra protection | ||
| const safeTableName = tableName.replace(/[^a-zA-Z0-9_]/g, ''); |
Copilot
AI
Jan 14, 2026
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.
Same security concern as with PRAGMA foreign_key_list - while regex sanitization helps, constructing raw SQL with string interpolation for SQLite PRAGMA commands could still be vulnerable. Validate that the sanitized table name exists in the discovered tables list before executing the query.
| const safeTableName = tableName.replace(/[^a-zA-Z0-9_]/g, ''); | |
| const safeTableName = tableName.replace(/[^a-zA-Z0-9_]/g, ''); | |
| // Validate that the sanitized table name exists in the database before using it in PRAGMA | |
| const tablesResult = await this.knex.raw("SELECT name FROM sqlite_master WHERE type = 'table'"); | |
| const tableRows: { name: string }[] = Array.isArray(tablesResult) | |
| ? (tablesResult as { name: string }[]) | |
| : ((tablesResult && (tablesResult as any)[0]) as { name: string }[] ?? []); | |
| const tableNames = tableRows.map((row) => row.name); | |
| if (!tableNames.includes(safeTableName)) { | |
| console.warn(`Could not introspect primary keys for SQLite table ${tableName}: table does not exist after sanitization (${safeTableName}).`); | |
| return primaryKeys; | |
| } |
packages/drivers/sql/src/index.ts
Outdated
| } | ||
| } | ||
| } catch (error) { | ||
| console.warn(`Could not introspect foreign keys for table ${tableName}:`, error); |
Copilot
AI
Jan 14, 2026
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.
The error message includes user-controlled table name which could expose sensitive information. Consider sanitizing or limiting what gets logged, or use a generic error message that doesn't include potentially sensitive database identifiers.
| console.warn(`Could not introspect foreign keys for table ${tableName}:`, error); | |
| console.warn('Could not introspect foreign keys for requested table.', error); |
packages/drivers/sql/src/index.ts
Outdated
| } | ||
| } | ||
| } catch (error) { | ||
| console.warn(`Could not introspect primary keys for table ${tableName}:`, error); |
Copilot
AI
Jan 14, 2026
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.
The error message includes user-controlled table name which could expose sensitive information. Consider sanitizing or limiting what gets logged, or use a generic error message that doesn't include potentially sensitive database identifiers.
| console.warn(`Could not introspect primary keys for table ${tableName}:`, error); | |
| console.warn('Could not introspect primary keys for a table:', error); |
packages/foundation/core/src/app.ts
Outdated
| throw new Error(`Driver for datasource '${datasourceName}' does not support schema introspection`); | ||
| } | ||
|
|
||
| console.log(`Introspecting database schema from datasource '${datasourceName}'...`); |
Copilot
AI
Jan 14, 2026
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.
Console.log statements in library code should use a proper logging framework or be configurable. Consider using a logger instance that can be controlled by the consuming application.
packages/foundation/core/src/app.ts
Outdated
| // Convert introspected schema to ObjectQL objects | ||
| const objects = convertIntrospectedSchemaToObjects(introspectedSchema, options); | ||
|
|
||
| console.log(`Discovered ${objects.length} tables. Registering as objects...`); |
Copilot
AI
Jan 14, 2026
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.
Console.log statements in library code should use a proper logging framework or be configurable. Consider using a logger instance that can be controlled by the consuming application.
|
@copilot 帮我修改 |
- Remove duplicate comment in util.ts - Add table existence validation before SQLite PRAGMA queries - Sanitize error messages to not expose table names - Make console.log messages more concise and consistent Co-authored-by: huangyiirene <7665279+huangyiirene@users.noreply.github.com>
- Fix table existence check to properly handle SQLite result arrays - Add array type guard for table listing query - Improve error message clarity for invalid table names Co-authored-by: huangyiirene <7665279+huangyiirene@users.noreply.github.com>
Database Schema Introspection Feature ✅
This PR implements automatic database schema introspection, enabling users to connect to existing databases without defining any metadata. The system automatically discovers tables, columns, data types, and foreign key relationships.
Completed Work
1. Update
@objectql/types- AddintrospectSchemamethod to Driver interfaceIntrospectedSchema,IntrospectedTable,IntrospectedColumn,IntrospectedForeignKeyinterfacesintrospectSchema()method2. Update
@objectql/driver-sql- Implement schema introspection for SQL databases3. Update
@objectql/core- Add helper to load introspected schemasintrospectAndRegister()method to ObjectQL classconvertIntrospectedSchemaToObjects()utility4. All tests passing (51 SQL driver tests + 83 core tests)
5. Complete documentation
docs/schema-introspection.mdexamples/connect-existing-database.ts6. Security and code quality improvements (all review comments addressed)
Key Features
✅ Zero Metadata Required - Connect to any existing SQL database instantly
✅ Automatic Type Mapping - Database types automatically mapped to ObjectQL field types
✅ Relationship Discovery - Foreign keys detected and converted to lookup fields
✅ Selective Introspection - Choose which tables to include or exclude
✅ Non-Destructive - Never modifies existing database schema
✅ Production Ready - Comprehensive tests and security validation
Usage Example
Test Coverage
Original prompt
💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.