@@ -25,6 +25,7 @@ type TestHarness struct {
2525 tmpDir string
2626 pgPort int
2727 dgPort int
28+ useDuckLake bool
2829 mu sync.Mutex
2930}
3031
@@ -36,21 +37,33 @@ type HarnessConfig struct {
3637 SkipPostgres bool
3738 // Verbose enables verbose logging
3839 Verbose bool
40+ // UseDuckLake enables DuckLake mode (requires ducklake-metadata and minio)
41+ UseDuckLake bool
42+ // DuckLakeMetadataPort is the port for the DuckLake metadata PostgreSQL (default: 35433)
43+ DuckLakeMetadataPort int
44+ // MinIOPort is the port for MinIO S3 API (default: 39000)
45+ MinIOPort int
3946}
4047
4148// DefaultConfig returns the default harness configuration
4249func DefaultConfig () HarnessConfig {
50+ // Default to DuckLake mode unless DUCKGRES_TEST_NO_DUCKLAKE is set
51+ useDuckLake := os .Getenv ("DUCKGRES_TEST_NO_DUCKLAKE" ) == ""
4352 return HarnessConfig {
44- PostgresPort : 35432 ,
45- SkipPostgres : false ,
46- Verbose : os .Getenv ("DUCKGRES_TEST_VERBOSE" ) != "" ,
53+ PostgresPort : 35432 ,
54+ SkipPostgres : false ,
55+ Verbose : os .Getenv ("DUCKGRES_TEST_VERBOSE" ) != "" ,
56+ UseDuckLake : useDuckLake ,
57+ DuckLakeMetadataPort : 35433 ,
58+ MinIOPort : 39000 ,
4759 }
4860}
4961
5062// NewTestHarness creates a new test harness
5163func NewTestHarness (cfg HarnessConfig ) (* TestHarness , error ) {
5264 h := & TestHarness {
53- pgPort : cfg .PostgresPort ,
65+ pgPort : cfg .PostgresPort ,
66+ useDuckLake : cfg .UseDuckLake ,
5467 }
5568
5669 // Create temp directory for Duckgres
@@ -61,7 +74,7 @@ func NewTestHarness(cfg HarnessConfig) (*TestHarness, error) {
6174 h .tmpDir = tmpDir
6275
6376 // Start Duckgres server
64- if err := h .startDuckgres (); err != nil {
77+ if err := h .startDuckgres (cfg ); err != nil {
6578 os .RemoveAll (tmpDir )
6679 return nil , fmt .Errorf ("failed to start Duckgres: %w" , err )
6780 }
@@ -90,7 +103,7 @@ func NewTestHarness(cfg HarnessConfig) (*TestHarness, error) {
90103}
91104
92105// startDuckgres starts the Duckgres server
93- func (h * TestHarness ) startDuckgres () error {
106+ func (h * TestHarness ) startDuckgres (harnessCfg HarnessConfig ) error {
94107 port := findAvailablePort ()
95108 h .dgPort = port
96109
@@ -111,6 +124,22 @@ func (h *TestHarness) startDuckgres() error {
111124 Users : map [string ]string {
112125 "testuser" : "testpass" ,
113126 },
127+ Extensions : []string {"ducklake" },
128+ }
129+
130+ // Configure DuckLake if enabled
131+ if harnessCfg .UseDuckLake {
132+ cfg .DuckLake = server.DuckLakeConfig {
133+ MetadataStore : fmt .Sprintf ("postgres:host=127.0.0.1 port=%d user=ducklake password=ducklake dbname=ducklake" , harnessCfg .DuckLakeMetadataPort ),
134+ ObjectStore : "s3://ducklake/data/" ,
135+ S3Provider : "config" ,
136+ S3Endpoint : fmt .Sprintf ("127.0.0.1:%d" , harnessCfg .MinIOPort ),
137+ S3AccessKey : "minioadmin" ,
138+ S3SecretKey : "minioadmin" ,
139+ S3Region : "us-east-1" ,
140+ S3UseSSL : false ,
141+ S3URLStyle : "path" ,
142+ }
114143 }
115144
116145 srv , err := server .New (cfg )
@@ -185,7 +214,17 @@ func (h *TestHarness) connectDuckgres() error {
185214}
186215
187216// loadFixtures loads the test schema and data into Duckgres
217+ // In DuckLake mode, tables are automatically created in ducklake.main
218+ // because the server runs "USE ducklake" to set the default catalog
188219func (h * TestHarness ) loadFixtures () error {
220+ // In DuckLake mode, drop existing tables first since metadata persists
221+ if h .useDuckLake {
222+ if err := h .cleanupDuckLakeTables (); err != nil {
223+ // Log but don't fail - tables might not exist
224+ fmt .Printf ("Warning: cleanup failed (may be OK): %v\n " , err )
225+ }
226+ }
227+
189228 // Read and execute schema
190229 schemaPath := filepath .Join (getTestDir (), "fixtures" , "schema.sql" )
191230 schemaSQL , err := os .ReadFile (schemaPath )
@@ -226,6 +265,45 @@ func (h *TestHarness) loadFixtures() error {
226265 return nil
227266}
228267
268+ // cleanupDuckLakeTables drops existing tables in DuckLake before loading fixtures
269+ func (h * TestHarness ) cleanupDuckLakeTables () error {
270+ // Drop views first (they depend on tables)
271+ views := []string {"order_details" , "user_stats" , "active_users" }
272+ for _ , v := range views {
273+ h .DuckgresDB .Exec (fmt .Sprintf ("DROP VIEW IF EXISTS %s" , v ))
274+ }
275+
276+ // Drop tables in reverse dependency order
277+ tables := []string {
278+ "test_schema.schema_test" ,
279+ "array_test" ,
280+ "documents" ,
281+ "metrics" ,
282+ "empty_table" ,
283+ "nullable_test" ,
284+ "json_data" ,
285+ "events" ,
286+ "sales" ,
287+ "categories" ,
288+ "order_items" ,
289+ "orders" ,
290+ "products" ,
291+ "users" ,
292+ "types_test" ,
293+ }
294+
295+ for _ , t := range tables {
296+ if _ , err := h .DuckgresDB .Exec (fmt .Sprintf ("DROP TABLE IF EXISTS %s" , t )); err != nil {
297+ // Ignore errors - table might not exist or schema might not exist
298+ }
299+ }
300+
301+ // Drop test schema
302+ h .DuckgresDB .Exec ("DROP SCHEMA IF EXISTS test_schema" )
303+
304+ return nil
305+ }
306+
229307// Close shuts down the test harness
230308func (h * TestHarness ) Close () error {
231309 h .mu .Lock ()
@@ -297,6 +375,39 @@ func IsPostgresRunning(port int) bool {
297375 return true
298376}
299377
378+ // IsDuckLakeInfraRunning checks if the DuckLake infrastructure (metadata postgres + minio) is running
379+ func IsDuckLakeInfraRunning (metadataPort , minioPort int ) bool {
380+ // Check DuckLake metadata PostgreSQL
381+ metaConn , err := net .DialTimeout ("tcp" , fmt .Sprintf ("127.0.0.1:%d" , metadataPort ), time .Second )
382+ if err != nil {
383+ return false
384+ }
385+ metaConn .Close ()
386+
387+ // Check MinIO
388+ minioConn , err := net .DialTimeout ("tcp" , fmt .Sprintf ("127.0.0.1:%d" , minioPort ), time .Second )
389+ if err != nil {
390+ return false
391+ }
392+ minioConn .Close ()
393+
394+ return true
395+ }
396+
397+ // WaitForDuckLakeInfra waits for DuckLake infrastructure to be ready
398+ func WaitForDuckLakeInfra (metadataPort , minioPort int , timeout time.Duration ) error {
399+ deadline := time .Now ().Add (timeout )
400+ for time .Now ().Before (deadline ) {
401+ if IsDuckLakeInfraRunning (metadataPort , minioPort ) {
402+ // Give MinIO a bit more time to initialize the bucket
403+ time .Sleep (500 * time .Millisecond )
404+ return nil
405+ }
406+ time .Sleep (500 * time .Millisecond )
407+ }
408+ return fmt .Errorf ("timeout waiting for DuckLake infrastructure (metadata:%d, minio:%d)" , metadataPort , minioPort )
409+ }
410+
300411// Helper functions
301412
302413func findAvailablePort () int {
0 commit comments