Skip to content

Commit 385e91d

Browse files
authored
feat(wren-launcher): introduce CLI tool for dbt integration (#1827)
1 parent afed539 commit 385e91d

File tree

16 files changed

+1846
-10
lines changed

16 files changed

+1846
-10
lines changed

docker/.env.example

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,3 +46,7 @@ AI_SERVICE_FORWARD_PORT=5555
4646

4747
# Wren UI
4848
EXPERIMENTAL_ENGINE_RUST_VERSION=false
49+
50+
# Wren Engine
51+
# OPTIONAL: set if you want to use local storage for the Wren Engine
52+
LOCAL_STORAGE=.

docker/docker-compose.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ services:
4141
- ${IBIS_SERVER_PORT}
4242
environment:
4343
WREN_ENGINE_ENDPOINT: http://wren-engine:${WREN_ENGINE_PORT}
44+
volumes:
45+
- ${LOCAL_STORAGE:-.}:/usr/src/app/data
4446
networks:
4547
- wren
4648

wren-launcher/commands/dbt.go

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
package commands
2+
3+
import (
4+
"flag"
5+
"os"
6+
7+
"github.com/Canner/WrenAI/wren-launcher/commands/dbt"
8+
"github.com/pterm/pterm"
9+
)
10+
11+
// DbtAutoConvert automatically searches for dbt profiles and catalog.json,
12+
// then converts them to WrenDataSource and Wren MDL format
13+
func DbtAutoConvert() {
14+
var opts struct {
15+
ProjectPath string
16+
OutputDir string
17+
ProfileName string
18+
Target string
19+
}
20+
21+
// Define command line flags
22+
flag.StringVar(&opts.ProjectPath, "path", "", "Path to the dbt project root directory")
23+
flag.StringVar(&opts.OutputDir, "output", "", "Output directory for generated JSON files")
24+
flag.StringVar(&opts.ProfileName, "profile", "", "Specific profile name to use (optional, uses first found if not provided)")
25+
flag.StringVar(&opts.Target, "target", "", "Specific target to use (optional, uses profile default if not provided)")
26+
flag.Parse()
27+
28+
// Validate required parameters
29+
if opts.ProjectPath == "" {
30+
pterm.Error.Println("Error: --path parameter is required")
31+
pterm.Info.Println("Usage: wren-launcher dbt-auto-convert --path /path/to/dbt/project --output /path/to/output")
32+
os.Exit(1)
33+
}
34+
35+
if opts.OutputDir == "" {
36+
pterm.Error.Println("Error: --output parameter is required")
37+
pterm.Info.Println("Usage: wren-launcher dbt-auto-convert --path /path/to/dbt/project --output /path/to/output")
38+
os.Exit(1)
39+
}
40+
41+
// ConvertOptions struct for core conversion logic
42+
convertOpts := dbt.ConvertOptions{
43+
ProjectPath: opts.ProjectPath,
44+
OutputDir: opts.OutputDir,
45+
ProfileName: opts.ProfileName,
46+
Target: opts.Target,
47+
RequireCatalog: true, // DbtAutoConvert requires catalog.json to exist
48+
}
49+
50+
// Call the core conversion logic
51+
_, err := dbt.ConvertDbtProjectCore(convertOpts)
52+
if err != nil {
53+
pterm.Error.Printf("Error: Conversion failed: %v\n", err)
54+
os.Exit(1)
55+
}
56+
}
57+
58+
// DbtConvertProject is a public wrapper function for processDbtProject to use
59+
// It converts a dbt project without requiring catalog.json to exist
60+
func DbtConvertProject(projectPath, outputDir, profileName, target string, usedByContainer bool) (*dbt.ConvertResult, error) {
61+
convertOpts := dbt.ConvertOptions{
62+
ProjectPath: projectPath,
63+
OutputDir: outputDir,
64+
ProfileName: profileName,
65+
Target: target,
66+
RequireCatalog: false, // Allow processDbtProject to continue without catalog.json
67+
UsedByContainer: usedByContainer,
68+
}
69+
70+
return dbt.ConvertDbtProjectCore(convertOpts)
71+
}
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
# How to Support a New Data Source
2+
3+
This document outlines the steps required to add support for a new data source to the dbt project converter.
4+
The target data source must be supported by both dbt and the Wren engine:
5+
- [dbt supported databases](https://docs.getdbt.com/docs/supported-data-platforms)
6+
- [Wren engine supported data sources](https://docs.getwren.ai/oss/wren_engine_api#tag/AthenaConnectionInfo)
7+
8+
## 1. Implement the DataSource Interface
9+
10+
The first step is to define a new struct for your data source and implement the `DataSource` interface defined in `data_source.go`.
11+
12+
The `DataSource` interface is as follows:
13+
14+
```go
15+
type DataSource interface {
16+
GetType() string
17+
Validate() error
18+
MapType(sourceType string) string
19+
}
20+
```
21+
22+
### Steps:
23+
24+
1. **Define Your Struct**: Create a new struct that represents the connection properties for your data source. The fields in this struct should correspond to the properties defined in the [Wren engine's API documentation](https://docs.getwren.ai/oss/wren_engine_api#tag/SnowflakeConnectionInfo) for the target data source.
25+
26+
For example, to add support for `Snowflake`, you would define the following struct:
27+
28+
```go
29+
type WrenSnowflakeDataSource struct {
30+
Account string `json:"account"`
31+
User string `json:"user"`
32+
Password string `json:"password"`
33+
Database string `json:"database"`
34+
Warehouse string `json:"warehouse"`
35+
// ... other properties
36+
}
37+
```
38+
39+
2. **Implement `GetType()`**: This method should return a string that identifies your data source type (e.g., `"snowflake"`).
40+
41+
3. **Implement `Validate()`**: This method should check if the essential properties of your data source are set and valid. Return an error if validation fails.
42+
43+
4. **Implement `MapType()`**: This method is crucial for mapping data types from the source system (as defined in `catalog.json`) to Wren's supported data types (e.g., `integer`, `varchar`, `timestamp`).
44+
45+
## 2. Add Conversion Logic in `data_source.go`
46+
47+
After implementing the interface, you need to integrate your new data source into the conversion logic. This is done by updating the `convertConnectionToDataSource` function in `data_source.go`.
48+
49+
Add a new `case` to the `switch` statement that matches the `type` field from the dbt `profiles.yml` file. This new case will be responsible for creating an instance of your new data source struct from the dbt connection details.
50+
51+
### Example:
52+
53+
```go
54+
// in data_source.go
55+
56+
func convertConnectionToDataSource(conn DbtConnection, dbtHomePath, profileName, outputName string) (DataSource, error) {
57+
switch strings.ToLower(conn.Type) {
58+
case "postgres", "postgresql":
59+
return convertToPostgresDataSource(conn)
60+
case "duckdb":
61+
return convertToLocalFileDataSource(conn, dbtHomePath)
62+
// Add your new case here
63+
case "snowflake":
64+
return convertToSnowflakeDataSource(conn) // Implement this function
65+
default:
66+
// ...
67+
}
68+
}
69+
70+
// Implement the conversion function
71+
func convertToSnowflakeDataSource(conn DbtConnection) (*WrenSnowflakeDataSource, error) {
72+
// Logic to extract snowflake properties from conn
73+
// and return a new *WrenSnowflakeDataSource
74+
}
75+
```
76+
77+
## 3. Handle the New Data Source in `ConvertDbtProjectCore`
78+
79+
The `ConvertDbtProjectCore` function in `converter.go` is responsible for generating the `wren-datasource.json` file. You must add your new data source to the `switch` statement within this function to ensure it is correctly serialized.
80+
81+
### Steps:
82+
83+
1. **Locate the `switch` statement**: Find the `switch typedDS := ds.(type)` block inside `ConvertDbtProjectCore`.
84+
2. **Add a new `case`**: Add a new `case` for your data source struct. Inside this case, construct the `wrenDataSource` map with the correct `type` and `properties`.
85+
86+
### Example:
87+
88+
```go
89+
// in converter.go's ConvertDbtProjectCore function
90+
91+
// ...
92+
switch typedDS := ds.(type) {
93+
case *WrenPostgresDataSource:
94+
// ...
95+
case *WrenLocalFileDataSource:
96+
// ...
97+
// Add your new case here
98+
case *WrenSnowflakeDataSource:
99+
wrenDataSource = map[string]interface{}{
100+
"type": "snowflake",
101+
"properties": map[string]interface{}{
102+
"account": typedDS.Account,
103+
"user": typedDS.User,
104+
"password": typedDS.Password,
105+
"database": typedDS.Database,
106+
"warehouse": typedDS.Warehouse,
107+
// ... other properties
108+
},
109+
}
110+
default:
111+
// ...
112+
}
113+
// ...
114+
```
115+
116+
**Note on File-Based Data Sources**: If your data source is file-based (like `duckdb`), you also need to add logic to set the `localStoragePath` variable correctly within `ConvertDbtProjectCore`. This path tells the Wren engine where to find the data files.

0 commit comments

Comments
 (0)