Skip to content

Commit 6a31d01

Browse files
authored
Merge pull request #65 from objectstack-ai/copilot/connect-database-without-metadata
2 parents bbbd46b + cb8ff4c commit 6a31d01

File tree

8 files changed

+1347
-1
lines changed

8 files changed

+1347
-1
lines changed

docs/schema-introspection.md

Lines changed: 315 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,315 @@
1+
# Schema Introspection - Connect to Existing Databases
2+
3+
ObjectQL now supports **automatic schema introspection**, allowing you to connect to an existing database without defining any metadata. The system will automatically discover tables, columns, data types, and relationships.
4+
5+
## Features
6+
7+
-**Zero Metadata Required**: Connect to any existing SQL database instantly
8+
-**Automatic Type Mapping**: Database types are automatically mapped to ObjectQL field types
9+
-**Relationship Discovery**: Foreign keys are automatically detected and converted to lookup fields
10+
-**Selective Introspection**: Choose which tables to include or exclude
11+
-**Non-Destructive**: Introspection never modifies your existing database schema
12+
13+
## Supported Databases
14+
15+
- PostgreSQL
16+
- MySQL / MySQL2
17+
- SQLite3
18+
19+
## Quick Start
20+
21+
### 1. Connect to Existing Database
22+
23+
```typescript
24+
import { ObjectQL } from '@objectql/core';
25+
import { SqlDriver } from '@objectql/driver-sql';
26+
27+
// Create driver for your existing database
28+
const driver = new SqlDriver({
29+
client: 'postgresql',
30+
connection: {
31+
host: 'localhost',
32+
database: 'my_existing_db',
33+
user: 'username',
34+
password: 'password'
35+
}
36+
});
37+
38+
// Initialize ObjectQL
39+
const app = new ObjectQL({
40+
datasources: { default: driver }
41+
});
42+
```
43+
44+
### 2. Introspect and Register Tables
45+
46+
```typescript
47+
// Automatically discover all tables
48+
const objects = await app.introspectAndRegister();
49+
50+
console.log(`Discovered ${objects.length} tables`);
51+
```
52+
53+
### 3. Use Discovered Objects
54+
55+
```typescript
56+
await app.init();
57+
58+
const ctx = app.createContext({ isSystem: true });
59+
60+
// Query existing data
61+
const users = await ctx.object('users').find({});
62+
63+
// Insert new records
64+
const newUser = await ctx.object('users').create({
65+
name: 'Alice',
66+
email: 'alice@example.com'
67+
});
68+
```
69+
70+
## Options
71+
72+
### Selective Table Introspection
73+
74+
```typescript
75+
// Only include specific tables
76+
await app.introspectAndRegister('default', {
77+
includeTables: ['users', 'orders', 'products']
78+
});
79+
80+
// Or exclude certain tables
81+
await app.introspectAndRegister('default', {
82+
excludeTables: ['migrations', 'sessions', 'logs']
83+
});
84+
```
85+
86+
### System Columns
87+
88+
By default, ObjectQL skips standard system columns (`id`, `created_at`, `updated_at`) during introspection since they're automatically handled by the framework.
89+
90+
```typescript
91+
// Include system columns in field definitions
92+
await app.introspectAndRegister('default', {
93+
skipSystemColumns: false
94+
});
95+
```
96+
97+
## Type Mapping
98+
99+
Database types are automatically mapped to ObjectQL field types:
100+
101+
| Database Type | ObjectQL Type |
102+
|--------------|---------------|
103+
| `VARCHAR`, `CHAR` | `text` |
104+
| `TEXT`, `LONGTEXT` | `textarea` |
105+
| `INTEGER`, `BIGINT`, `INT` | `number` |
106+
| `FLOAT`, `DOUBLE`, `DECIMAL` | `number` |
107+
| `BOOLEAN`, `BOOL` | `boolean` |
108+
| `DATE` | `date` |
109+
| `DATETIME`, `TIMESTAMP` | `datetime` |
110+
| `TIME` | `time` |
111+
| `JSON`, `JSONB` | `object` |
112+
113+
## Relationship Detection
114+
115+
Foreign key constraints are automatically detected and converted to `lookup` fields:
116+
117+
**Database Schema:**
118+
```sql
119+
CREATE TABLE orders (
120+
id VARCHAR PRIMARY KEY,
121+
customer_id VARCHAR NOT NULL,
122+
total DECIMAL(10, 2),
123+
FOREIGN KEY (customer_id) REFERENCES customers(id)
124+
);
125+
```
126+
127+
**Discovered ObjectQL Schema:**
128+
```typescript
129+
{
130+
name: 'orders',
131+
fields: {
132+
customer_id: {
133+
type: 'lookup',
134+
reference_to: 'customers',
135+
required: true
136+
},
137+
total: {
138+
type: 'number'
139+
}
140+
}
141+
}
142+
```
143+
144+
## Advanced Usage
145+
146+
### Inspect Schema Without Registering
147+
148+
```typescript
149+
const driver = app.datasource('default');
150+
const schema = await driver.introspectSchema();
151+
152+
// schema.tables contains all discovered table metadata
153+
console.log(schema.tables['users'].columns);
154+
console.log(schema.tables['users'].foreignKeys);
155+
```
156+
157+
### Convert Schema Manually
158+
159+
```typescript
160+
import { convertIntrospectedSchemaToObjects } from '@objectql/core';
161+
162+
const schema = await driver.introspectSchema();
163+
const objects = convertIntrospectedSchemaToObjects(schema, {
164+
excludeTables: ['temp_tables']
165+
});
166+
167+
// Register manually
168+
objects.forEach(obj => app.registerObject(obj));
169+
```
170+
171+
## Use Cases
172+
173+
### 1. Legacy Database Integration
174+
175+
Connect ObjectQL to an existing legacy database without rewriting the schema:
176+
177+
```typescript
178+
const app = new ObjectQL({ datasources: { legacy: legacyDriver } });
179+
await app.introspectAndRegister('legacy');
180+
await app.init();
181+
182+
// Now use ObjectQL's modern API with your legacy database
183+
const records = await ctx.object('old_table_name').find({});
184+
```
185+
186+
### 2. Database Migration
187+
188+
Introspect an existing database to generate ObjectQL metadata for version control:
189+
190+
```typescript
191+
const schema = await driver.introspectSchema();
192+
const objects = convertIntrospectedSchemaToObjects(schema);
193+
194+
// Export to YAML files for your project
195+
objects.forEach(obj => {
196+
fs.writeFileSync(
197+
`src/objects/${obj.name}.object.yml`,
198+
yaml.dump(obj)
199+
);
200+
});
201+
```
202+
203+
### 3. Multi-Database Applications
204+
205+
Connect to multiple existing databases simultaneously:
206+
207+
```typescript
208+
const app = new ObjectQL({
209+
datasources: {
210+
main: mainDbDriver,
211+
analytics: analyticsDbDriver,
212+
archive: archiveDbDriver
213+
}
214+
});
215+
216+
await app.introspectAndRegister('main');
217+
await app.introspectAndRegister('analytics', {
218+
excludeTables: ['raw_logs']
219+
});
220+
await app.introspectAndRegister('archive', {
221+
includeTables: ['historical_orders']
222+
});
223+
```
224+
225+
## Limitations
226+
227+
1. **SQLite Foreign Keys**: SQLite requires `PRAGMA foreign_keys = ON` to properly detect foreign key constraints
228+
2. **Complex Types**: Some database-specific types may be mapped to generic ObjectQL types
229+
3. **Computed Columns**: Virtual/computed columns are introspected as regular fields
230+
4. **Indexes**: While unique constraints are detected, regular indexes are not yet included in introspection
231+
232+
## API Reference
233+
234+
### `app.introspectAndRegister(datasourceName?, options?)`
235+
236+
Introspect a database and automatically register discovered tables as ObjectQL objects.
237+
238+
**Parameters:**
239+
- `datasourceName` (string, optional): Name of the datasource (default: `'default'`)
240+
- `options` (object, optional):
241+
- `includeTables` (string[]): Only include these tables
242+
- `excludeTables` (string[]): Exclude these tables
243+
- `skipSystemColumns` (boolean): Skip `id`, `created_at`, `updated_at` (default: `true`)
244+
245+
**Returns:** `Promise<ObjectConfig[]>` - Array of registered object configurations
246+
247+
### `driver.introspectSchema()`
248+
249+
Low-level method to introspect the database schema.
250+
251+
**Returns:** `Promise<IntrospectedSchema>` - Complete schema metadata
252+
253+
### `convertIntrospectedSchemaToObjects(schema, options?)`
254+
255+
Convert introspected schema to ObjectQL object configurations.
256+
257+
**Parameters:**
258+
- `schema` (IntrospectedSchema): Schema from `driver.introspectSchema()`
259+
- `options` (object, optional): Same as `introspectAndRegister`
260+
261+
**Returns:** `ObjectConfig[]` - Array of object configurations
262+
263+
## Examples
264+
265+
See [examples/connect-existing-database.ts](../examples/connect-existing-database.ts) for a complete working example.
266+
267+
## Chinese Documentation (中文文档)
268+
269+
### 快速开始
270+
271+
```typescript
272+
import { ObjectQL } from '@objectql/core';
273+
import { SqlDriver } from '@objectql/driver-sql';
274+
275+
// 1. 连接到现有数据库
276+
const driver = new SqlDriver({
277+
client: 'postgresql',
278+
connection: {
279+
host: 'localhost',
280+
database: '现有数据库',
281+
user: '用户名',
282+
password: '密码'
283+
}
284+
});
285+
286+
const app = new ObjectQL({
287+
datasources: { default: driver }
288+
});
289+
290+
// 2. 自动发现数据库表结构
291+
const objects = await app.introspectAndRegister('default', {
292+
excludeTables: ['migrations'] // 排除不需要的表
293+
});
294+
295+
console.log(`发现了 ${objects.length} 个表`);
296+
297+
// 3. 初始化并使用
298+
await app.init();
299+
const ctx = app.createContext({ isSystem: true });
300+
301+
// 查询现有数据
302+
const users = await ctx.object('users').find({});
303+
304+
// 创建新记录
305+
const newUser = await ctx.object('users').create({
306+
name: '张三',
307+
email: 'zhangsan@example.com'
308+
});
309+
```
310+
311+
**主要特性:**
312+
- ✅ 无需定义任何元数据即可连接现有数据库
313+
- ✅ 自动识别表、字段、数据类型和外键关系
314+
- ✅ 支持 PostgreSQL、MySQL、SQLite
315+
- ✅ 完全非破坏性操作,不修改现有数据库结构

0 commit comments

Comments
 (0)