Skip to content

Conversation

Copy link
Contributor

Copilot AI commented Jan 14, 2026

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 - Add introspectSchema method to Driver interface

    • Added IntrospectedSchema, IntrospectedTable, IntrospectedColumn, IntrospectedForeignKey interfaces
    • Extended Driver interface with optional introspectSchema() method
  • 2. Update @objectql/driver-sql - Implement schema introspection for SQL databases

    • 2a. Implemented table discovery for PostgreSQL, MySQL, and SQLite
    • 2b. Implemented column introspection with type and constraint detection
    • 2c. Implemented foreign key relationship discovery
    • 2d. Created type mapping from SQL to ObjectQL field types
    • 2e. Comprehensive testing with 7 test cases for introspection
  • 3. Update @objectql/core - Add helper to load introspected schemas

    • 3a. Added introspectAndRegister() method to ObjectQL class
    • 3b. Implemented convertIntrospectedSchemaToObjects() utility
    • 3c. Support for selective table inclusion/exclusion
    • 3d. Comprehensive testing with 7 test cases for conversion
  • 4. All tests passing (51 SQL driver tests + 83 core tests)

  • 5. Complete documentation

    • English and Chinese documentation in docs/schema-introspection.md
    • Working example in examples/connect-existing-database.ts
    • API reference and use case examples
  • 6. Security and code quality improvements (all review comments addressed)

    • ✅ Removed duplicate comment
    • ✅ Enhanced SQLite PRAGMA security with table existence validation
    • ✅ Sanitized error messages to not expose sensitive information
    • ✅ Made logging more concise
    • ✅ Fixed SQLite result array handling
    • ✅ Improved error message clarity
    • CodeQL security check passed with 0 vulnerabilities

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

const app = new ObjectQL({ datasources: { default: driver } });
await app.introspectAndRegister('default', {
    excludeTables: ['migrations', 'sessions']
});
await app.init();

// Use discovered tables immediately
const users = await ctx.object('users').find({});

Test Coverage

  • ✅ 7 introspection tests (table/column/FK discovery)
  • ✅ 7 conversion tests (type mapping, filtering)
  • ✅ 51 SQL driver tests (all passing)
  • ✅ 83 core tests (all passing)
  • ✅ 0 security vulnerabilities (CodeQL verified)
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.

Copilot AI and others added 2 commits January 14, 2026 11:26
- 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>
Copilot AI changed the title [WIP] Add direct database connection without metadata definition feat: Add database schema introspection for existing databases Jan 14, 2026
Copilot AI requested a review from hotlong January 14, 2026 11:43
@huangyiirene huangyiirene requested review from Copilot and removed request for hotlong January 14, 2026 11:47
Copy link
Contributor

Copilot AI left a 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
Copy link

Copilot AI Jan 14, 2026

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.

Suggested change
// Add max length for text fields

Copilot uses AI. Check for mistakes.
Comment on lines 641 to 646
// 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) {
Copy link

Copilot AI Jan 14, 2026

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.

Suggested change
// 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) {

Copilot uses AI. Check for mistakes.
} 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, '');
Copy link

Copilot AI Jan 14, 2026

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.

Suggested change
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;
}

Copilot uses AI. Check for mistakes.
}
}
} catch (error) {
console.warn(`Could not introspect foreign keys for table ${tableName}:`, error);
Copy link

Copilot AI Jan 14, 2026

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.

Suggested change
console.warn(`Could not introspect foreign keys for table ${tableName}:`, error);
console.warn('Could not introspect foreign keys for requested table.', error);

Copilot uses AI. Check for mistakes.
}
}
} catch (error) {
console.warn(`Could not introspect primary keys for table ${tableName}:`, error);
Copy link

Copilot AI Jan 14, 2026

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.

Suggested change
console.warn(`Could not introspect primary keys for table ${tableName}:`, error);
console.warn('Could not introspect primary keys for a table:', error);

Copilot uses AI. Check for mistakes.
throw new Error(`Driver for datasource '${datasourceName}' does not support schema introspection`);
}

console.log(`Introspecting database schema from datasource '${datasourceName}'...`);
Copy link

Copilot AI Jan 14, 2026

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 uses AI. Check for mistakes.
// Convert introspected schema to ObjectQL objects
const objects = convertIntrospectedSchemaToObjects(introspectedSchema, options);

console.log(`Discovered ${objects.length} tables. Registering as objects...`);
Copy link

Copilot AI Jan 14, 2026

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 uses AI. Check for mistakes.
@huangyiirene
Copy link
Contributor

@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>
Copy link
Contributor Author

Copilot AI commented Jan 14, 2026

@copilot 帮我修改

已修复所有代码审查问题 (Fixed all code review issues in commit 9ec9760):

  1. ✅ 移除了 util.ts 中的重复注释
  2. ✅ 增强了 SQLite PRAGMA 查询的安全性,在执行前验证表是否存在
  3. ✅ 清理了错误消息,避免暴露敏感的表名信息
  4. ✅ 优化了日志输出,使其更加简洁

所有测试通过 (134 tests passing)。

- 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>
Copilot AI requested a review from huangyiirene January 14, 2026 12:07
@huangyiirene huangyiirene marked this pull request as ready for review January 14, 2026 12:49
@huangyiirene huangyiirene merged commit 6a31d01 into main Jan 14, 2026
2 checks passed
@huangyiirene huangyiirene deleted the copilot/connect-database-without-metadata branch January 14, 2026 12:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants