or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

configuration.mdindex.mdjdbc-url-pattern.mdpostgresql-container.mdpostgresql-variants.mdr2dbc-support.md
tile.json

postgresql-variants.mddocs/

PostgreSQL Variants

Testcontainers PostgreSQL module supports multiple PostgreSQL distributions with specialized extensions including PostGIS (geographic information systems), TimescaleDB (time-series data), and PgVector (vector similarity search). Each variant has its own provider for automatic container creation via JDBC URL patterns and can also be used directly with PostgreSQLContainer.

Complete API Reference

PostGIS Support

PostGIS adds geographic information system (GIS) capabilities to PostgreSQL, enabling storage and querying of geographic objects.

/**
 * Provider for creating PostGIS containers via JDBC URL patterns
 * PostGIS extends PostgreSQL with geographic object support
 */
public class PostgisContainerProvider extends JdbcDatabaseContainerProvider {

    // URL parameter names
    public static final String USER_PARAM = "user";
    public static final String PASSWORD_PARAM = "password";

    /**
     * Check if this provider supports the given database type
     * @param databaseType Database type string
     * @return true if databaseType is "postgis", false otherwise
     */
    public boolean supports(String databaseType);

    /**
     * Create a new PostGIS container with default image (postgis/postgis:12-3.0)
     * @return New JdbcDatabaseContainer with PostGIS
     * @throws ContainerLaunchException if container fails to start
     */
    public JdbcDatabaseContainer newInstance();

    /**
     * Create a new PostGIS container with specific version tag
     * @param tag PostGIS version tag (e.g., "16-3.4", "15-3.3-alpine")
     * @return New JdbcDatabaseContainer with PostGIS
     * @throws IllegalArgumentException if tag is invalid
     * @throws ContainerLaunchException if container fails to start
     */
    public JdbcDatabaseContainer newInstance(String tag);

    /**
     * Create a new PostGIS container from a parsed JDBC connection URL
     * Extracts database name, user, password, and other parameters from URL
     * @param connectionUrl Parsed connection URL object
     * @return New JdbcDatabaseContainer configured from URL
     * @throws IllegalArgumentException if connectionUrl is invalid
     */
    public JdbcDatabaseContainer newInstance(ConnectionUrl connectionUrl);
}

JDBC URL Format:

jdbc:tc:postgis:<version>://<host>/<database>?user=<user>&password=<password>

Usage Examples:

import org.testcontainers.containers.PostgreSQLContainer;
import org.testcontainers.utility.DockerImageName;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;

// Using PostgreSQLContainer with PostGIS image
public class PostGISTest {
    public void testWithPostGIS() throws Exception {
        DockerImageName postgisImage = DockerImageName
            .parse("postgis/postgis:16-3.4")
            .asCompatibleSubstituteFor("postgres");

        try (PostgreSQLContainer<?> postgis = new PostgreSQLContainer<>(postgisImage)) {
            postgis.start();

            String jdbcUrl = postgis.getJdbcUrl();
            try (Connection conn = DriverManager.getConnection(
                    jdbcUrl,
                    postgis.getUsername(),
                    postgis.getPassword())) {

                // Enable PostGIS extension
                try (Statement stmt = conn.createStatement()) {
                    stmt.execute("CREATE EXTENSION IF NOT EXISTS postgis");

                    // Query PostGIS version
                    try (ResultSet rs = stmt.executeQuery("SELECT PostGIS_Version()")) {
                        rs.next();
                        String version = rs.getString(1);
                        // PostGIS version info
                    }

                    // Create table with geography column
                    stmt.execute(
                        "CREATE TABLE locations (" +
                        "id SERIAL PRIMARY KEY, " +
                        "name VARCHAR(100), " +
                        "coordinates GEOGRAPHY(POINT, 4326))"
                    );

                    // Insert geographic data
                    stmt.execute(
                        "INSERT INTO locations (name, coordinates) " +
                        "VALUES ('New York', ST_GeogFromText('POINT(-74.0060 40.7128)'))"
                    );

                    // Spatial query
                    try (ResultSet rs = stmt.executeQuery(
                        "SELECT name, ST_AsText(coordinates) " +
                        "FROM locations")) {
                        while (rs.next()) {
                            String name = rs.getString(1);
                            String coords = rs.getString(2);
                            // name: "New York", coords: "POINT(-74.006 40.7128)"
                        }
                    }
                }
            }
        }
    }
}

PostGIS with JDBC URL Pattern:

// Automatic container management via JDBC URL
public void testPostGISWithJdbcUrl() throws Exception {
    String jdbcUrl = "jdbc:tc:postgis:16-3.4:///testdb?user=test&password=test";

    try (Connection conn = DriverManager.getConnection(jdbcUrl)) {
        try (Statement stmt = conn.createStatement()) {
            stmt.execute("CREATE EXTENSION IF NOT EXISTS postgis");

            // Use PostGIS functions
            try (ResultSet rs = stmt.executeQuery(
                "SELECT ST_Distance(" +
                "ST_GeogFromText('POINT(-74.0060 40.7128)'), " +  // New York
                "ST_GeogFromText('POINT(-118.2437 34.0522)')" +     // Los Angeles
                ")")) {
                rs.next();
                double distance = rs.getDouble(1);
                // Distance in meters between NYC and LA
            }
        }
    }
}

Available PostGIS Versions:

  • postgis/postgis:16-3.4 - PostgreSQL 16 with PostGIS 3.4
  • postgis/postgis:16-3.4-alpine - Alpine variant (smaller image)
  • postgis/postgis:15-3.3 - PostgreSQL 15 with PostGIS 3.3
  • postgis/postgis:14-3.2 - PostgreSQL 14 with PostGIS 3.2
  • postgis/postgis:12-3.0 - PostgreSQL 12 with PostGIS 3.0 (default)

TimescaleDB Support

TimescaleDB extends PostgreSQL with time-series database capabilities, optimized for handling time-series data at scale.

/**
 * Provider for creating TimescaleDB containers via JDBC URL patterns
 * TimescaleDB extends PostgreSQL with time-series capabilities
 */
public class TimescaleDBContainerProvider extends JdbcDatabaseContainerProvider {

    // URL parameter names
    public static final String USER_PARAM = "user";
    public static final String PASSWORD_PARAM = "password";

    /**
     * Check if this provider supports the given database type
     * @param databaseType Database type string
     * @return true if databaseType is "timescaledb", false otherwise
     */
    public boolean supports(String databaseType);

    /**
     * Create a new TimescaleDB container with default image (timescale/timescaledb:2.1.0-pg11)
     * @return New JdbcDatabaseContainer with TimescaleDB
     * @throws ContainerLaunchException if container fails to start
     */
    public JdbcDatabaseContainer newInstance();

    /**
     * Create a new TimescaleDB container with specific version tag
     * @param tag TimescaleDB version tag (e.g., "2.14.2-pg16", "2.13.0-pg15")
     * @return New JdbcDatabaseContainer with TimescaleDB
     * @throws IllegalArgumentException if tag is invalid
     * @throws ContainerLaunchException if container fails to start
     */
    public JdbcDatabaseContainer newInstance(String tag);

    /**
     * Create a new TimescaleDB container from a parsed JDBC connection URL
     * Extracts database name, user, password, and other parameters from URL
     * @param connectionUrl Parsed connection URL object
     * @return New JdbcDatabaseContainer configured from URL
     * @throws IllegalArgumentException if connectionUrl is invalid
     */
    public JdbcDatabaseContainer newInstance(ConnectionUrl connectionUrl);
}

JDBC URL Format:

jdbc:tc:timescaledb:<version>://<host>/<database>?user=<user>&password=<password>

Usage Examples:

import org.testcontainers.containers.PostgreSQLContainer;
import org.testcontainers.utility.DockerImageName;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.Statement;
import java.sql.Timestamp;

// Using PostgreSQLContainer with TimescaleDB image
public class TimescaleDBTest {
    public void testWithTimescaleDB() throws Exception {
        DockerImageName timescaleImage = DockerImageName
            .parse("timescale/timescaledb:2.14.2-pg16")
            .asCompatibleSubstituteFor("postgres");

        try (PostgreSQLContainer<?> timescale = new PostgreSQLContainer<>(timescaleImage)) {
            timescale.start();

            String jdbcUrl = timescale.getJdbcUrl();
            try (Connection conn = DriverManager.getConnection(
                    jdbcUrl,
                    timescale.getUsername(),
                    timescale.getPassword())) {

                try (Statement stmt = conn.createStatement()) {
                    // Enable TimescaleDB extension
                    stmt.execute("CREATE EXTENSION IF NOT EXISTS timescaledb");

                    // Create regular table
                    stmt.execute(
                        "CREATE TABLE sensor_data (" +
                        "time TIMESTAMPTZ NOT NULL, " +
                        "sensor_id INTEGER NOT NULL, " +
                        "temperature DOUBLE PRECISION, " +
                        "humidity DOUBLE PRECISION)"
                    );

                    // Convert to hypertable (time-series optimized)
                    stmt.execute(
                        "SELECT create_hypertable('sensor_data', 'time')"
                    );

                    // Insert time-series data
                    String insertSql =
                        "INSERT INTO sensor_data (time, sensor_id, temperature, humidity) " +
                        "VALUES (?, ?, ?, ?)";

                    try (PreparedStatement pstmt = conn.prepareStatement(insertSql)) {
                        long now = System.currentTimeMillis();
                        for (int i = 0; i < 100; i++) {
                            pstmt.setTimestamp(1, new Timestamp(now - (i * 60000))); // Every minute
                            pstmt.setInt(2, 1);
                            pstmt.setDouble(3, 20.0 + Math.random() * 10);
                            pstmt.setDouble(4, 40.0 + Math.random() * 20);
                            pstmt.addBatch();
                        }
                        pstmt.executeBatch();
                    }

                    // Time-series query with time_bucket
                    String querySql =
                        "SELECT time_bucket('5 minutes', time) AS bucket, " +
                        "AVG(temperature) as avg_temp, " +
                        "MAX(temperature) as max_temp " +
                        "FROM sensor_data " +
                        "WHERE sensor_id = 1 " +
                        "GROUP BY bucket " +
                        "ORDER BY bucket DESC";

                    try (ResultSet rs = stmt.executeQuery(querySql)) {
                        while (rs.next()) {
                            Timestamp bucket = rs.getTimestamp("bucket");
                            double avgTemp = rs.getDouble("avg_temp");
                            double maxTemp = rs.getDouble("max_temp");
                            // Time-bucketed aggregates
                        }
                    }
                }
            }
        }
    }
}

TimescaleDB with JDBC URL Pattern:

// Automatic container management via JDBC URL
public void testTimescaleDBWithJdbcUrl() throws Exception {
    String jdbcUrl = "jdbc:tc:timescaledb:2.14.2-pg16:///testdb";

    try (Connection conn = DriverManager.getConnection(jdbcUrl)) {
        try (Statement stmt = conn.createStatement()) {
            stmt.execute("CREATE EXTENSION IF NOT EXISTS timescaledb");

            // Create and configure hypertable
            stmt.execute(
                "CREATE TABLE metrics (" +
                "time TIMESTAMPTZ NOT NULL, " +
                "value DOUBLE PRECISION)"
            );
            stmt.execute("SELECT create_hypertable('metrics', 'time')");

            // Use time-series features
            // ...
        }
    }
}

Available TimescaleDB Versions:

  • timescale/timescaledb:2.14.2-pg16 - TimescaleDB 2.14.2 on PostgreSQL 16
  • timescale/timescaledb:2.13.1-pg15 - TimescaleDB 2.13.1 on PostgreSQL 15
  • timescale/timescaledb:2.11.2-pg14 - TimescaleDB 2.11.2 on PostgreSQL 14
  • timescale/timescaledb:2.1.0-pg11 - TimescaleDB 2.1.0 on PostgreSQL 11 (default)

PgVector Support

PgVector adds vector similarity search capabilities to PostgreSQL, enabling efficient storage and querying of embeddings for machine learning and AI applications.

/**
 * Provider for creating PgVector containers via JDBC URL patterns
 * PgVector extends PostgreSQL with vector similarity search
 */
public class PgVectorContainerProvider extends JdbcDatabaseContainerProvider {

    // URL parameter names
    public static final String USER_PARAM = "user";
    public static final String PASSWORD_PARAM = "password";

    /**
     * Check if this provider supports the given database type
     * @param databaseType Database type string
     * @return true if databaseType is "pgvector", false otherwise
     */
    public boolean supports(String databaseType);

    /**
     * Create a new PgVector container with default image (pgvector/pgvector:pg16)
     * @return New JdbcDatabaseContainer with PgVector
     * @throws ContainerLaunchException if container fails to start
     */
    public JdbcDatabaseContainer newInstance();

    /**
     * Create a new PgVector container with specific version tag
     * @param tag PgVector version tag (e.g., "pg16", "pg15", "pg14")
     * @return New JdbcDatabaseContainer with PgVector
     * @throws IllegalArgumentException if tag is invalid
     * @throws ContainerLaunchException if container fails to start
     */
    public JdbcDatabaseContainer newInstance(String tag);

    /**
     * Create a new PgVector container from a parsed JDBC connection URL
     * Extracts database name, user, password, and other parameters from URL
     * @param connectionUrl Parsed connection URL object
     * @return New JdbcDatabaseContainer configured from URL
     * @throws IllegalArgumentException if connectionUrl is invalid
     */
    public JdbcDatabaseContainer newInstance(ConnectionUrl connectionUrl);
}

JDBC URL Format:

jdbc:tc:pgvector://<host>/<database>?user=<user>&password=<password>

Usage Examples:

import org.testcontainers.containers.PostgreSQLContainer;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.Statement;

// Using PostgreSQLContainer with PgVector image
public class PgVectorTest {
    public void testWithPgVector() throws Exception {
        try (PostgreSQLContainer<?> pgvector = new PostgreSQLContainer<>("pgvector/pgvector:pg16")) {
            pgvector.start();

            String jdbcUrl = pgvector.getJdbcUrl();
            try (Connection conn = DriverManager.getConnection(
                    jdbcUrl,
                    pgvector.getUsername(),
                    pgvector.getPassword())) {

                try (Statement stmt = conn.createStatement()) {
                    // Enable vector extension
                    stmt.execute("CREATE EXTENSION IF NOT EXISTS vector");

                    // Create table with vector column
                    stmt.execute(
                        "CREATE TABLE embeddings (" +
                        "id SERIAL PRIMARY KEY, " +
                        "content TEXT, " +
                        "embedding vector(3))"  // 3-dimensional vectors
                    );

                    // Create index for fast similarity search
                    stmt.execute(
                        "CREATE INDEX ON embeddings " +
                        "USING ivfflat (embedding vector_cosine_ops) " +
                        "WITH (lists = 100)"
                    );

                    // Insert vector data
                    String insertSql =
                        "INSERT INTO embeddings (content, embedding) VALUES (?, ?::vector)";

                    try (PreparedStatement pstmt = conn.prepareStatement(insertSql)) {
                        pstmt.setString(1, "cat");
                        pstmt.setString(2, "[1, 2, 3]");
                        pstmt.executeUpdate();

                        pstmt.setString(1, "dog");
                        pstmt.setString(2, "[1, 2, 4]");
                        pstmt.executeUpdate();

                        pstmt.setString(1, "tree");
                        pstmt.setString(2, "[10, 20, 30]");
                        pstmt.executeUpdate();
                    }

                    // Vector similarity search (find nearest neighbors)
                    String searchSql =
                        "SELECT content, embedding, " +
                        "embedding <=> ?::vector AS distance " +
                        "FROM embeddings " +
                        "ORDER BY embedding <=> ?::vector " +
                        "LIMIT 2";

                    try (PreparedStatement pstmt = conn.prepareStatement(searchSql)) {
                        String queryVector = "[1, 2, 3.5]";
                        pstmt.setString(1, queryVector);
                        pstmt.setString(2, queryVector);

                        try (ResultSet rs = pstmt.executeQuery()) {
                            while (rs.next()) {
                                String content = rs.getString("content");
                                String embedding = rs.getString("embedding");
                                double distance = rs.getDouble("distance");
                                // Most similar: "dog" (distance ~0.5), then "cat" (distance ~0.5)
                            }
                        }
                    }

                    // Different distance metrics
                    // <-> : Euclidean distance (L2)
                    // <=> : Cosine distance
                    // <#> : Inner product (negative)

                    // Cosine similarity search
                    try (ResultSet rs = stmt.executeQuery(
                        "SELECT content, " +
                        "1 - (embedding <=> '[1,2,3]'::vector) AS cosine_similarity " +
                        "FROM embeddings " +
                        "ORDER BY embedding <=> '[1,2,3]'::vector " +
                        "LIMIT 3")) {
                        while (rs.next()) {
                            String content = rs.getString("content");
                            double similarity = rs.getDouble("cosine_similarity");
                            // Ordered by similarity
                        }
                    }
                }
            }
        }
    }
}

PgVector with JDBC URL Pattern:

// Automatic container management via JDBC URL
public void testPgVectorWithJdbcUrl() throws Exception {
    String jdbcUrl = "jdbc:tc:pgvector:///testdb";

    try (Connection conn = DriverManager.getConnection(jdbcUrl)) {
        try (Statement stmt = conn.createStatement()) {
            stmt.execute("CREATE EXTENSION IF NOT EXISTS vector");

            // Create table with higher-dimensional vectors
            stmt.execute(
                "CREATE TABLE documents (" +
                "id SERIAL PRIMARY KEY, " +
                "text TEXT, " +
                "embedding vector(1536))"  // OpenAI embedding dimension
            );

            // Use vector operations
            // ...
        }
    }
}

Available PgVector Versions:

  • pgvector/pgvector:pg16 - PgVector on PostgreSQL 16 (default)
  • pgvector/pgvector:pg15 - PgVector on PostgreSQL 15
  • pgvector/pgvector:pg14 - PgVector on PostgreSQL 14
  • pgvector/pgvector:0.5.1-pg16 - Specific PgVector version

Vector Operations:

  • <-> - Euclidean distance (L2)
  • <=> - Cosine distance
  • <#> - Inner product (negative)

Index Types:

  • ivfflat - Inverted file with flat compression (faster build, less accurate)
  • hnsw - Hierarchical Navigable Small World (slower build, more accurate)

Direct PostgreSQLContainer Usage

All variants can also be used directly with PostgreSQLContainer:

// PostGIS
DockerImageName postgisImage = DockerImageName
    .parse("postgis/postgis:16-3.4")
    .asCompatibleSubstituteFor("postgres");
PostgreSQLContainer<?> postgis = new PostgreSQLContainer<>(postgisImage);

// TimescaleDB
DockerImageName timescaleImage = DockerImageName
    .parse("timescale/timescaledb:2.14.2-pg16")
    .asCompatibleSubstituteFor("postgres");
PostgreSQLContainer<?> timescale = new PostgreSQLContainer<>(timescaleImage);

// PgVector (already compatible)
PostgreSQLContainer<?> pgvector = new PostgreSQLContainer<>("pgvector/pgvector:pg16");

Comparison of Variants

FeatureStandard PostgreSQLPostGISTimescaleDBPgVector
Geographic queries
Time-series optimization
Vector similarity search
Standard SQL
JDBC support
R2DBC support

Provider Registration

All variant providers are registered via Java SPI in:

META-INF/services/org.testcontainers.containers.JdbcDatabaseContainerProvider

Registered providers:

  • org.testcontainers.containers.PostgreSQLContainerProvider
  • org.testcontainers.containers.PostgisContainerProvider
  • org.testcontainers.containers.TimescaleDBContainerProvider
  • org.testcontainers.containers.PgVectorContainerProvider

Choosing the Right Variant

Use Standard PostgreSQL when:

  • Testing general SQL database functionality
  • No specialized extensions needed
  • Smallest image size preferred

Use PostGIS when:

  • Working with geographic data (points, lines, polygons)
  • Performing spatial queries (distance, intersection, containment)
  • Building location-based features

Use TimescaleDB when:

  • Handling time-series data (metrics, events, logs)
  • Need time-based aggregations and downsampling
  • Optimizing for time-range queries

Use PgVector when:

  • Building AI/ML applications with embeddings
  • Implementing similarity search
  • Working with vector representations of data

Error Handling and Troubleshooting

PostGIS Extension Errors

Extension not found:

try (PostgreSQLContainer<?> postgis = new PostgreSQLContainer<>(
        DockerImageName.parse("postgis/postgis:16-3.4")
            .asCompatibleSubstituteFor("postgres"))) {
    postgis.start();
    
    try (Connection conn = DriverManager.getConnection(
            postgis.getJdbcUrl(),
            postgis.getUsername(),
            postgis.getPassword());
         Statement stmt = conn.createStatement()) {
        
        // Verify PostGIS extension is available
        try (ResultSet rs = stmt.executeQuery(
                "SELECT COUNT(*) FROM pg_available_extensions WHERE name = 'postgis'")) {
            rs.next();
            if (rs.getInt(1) == 0) {
                throw new IllegalStateException("PostGIS extension not available in image");
            }
        }
        
        // Enable extension with error handling
        try {
            stmt.execute("CREATE EXTENSION IF NOT EXISTS postgis");
        } catch (SQLException e) {
            System.err.println("Failed to enable PostGIS: " + e.getMessage());
            System.err.println("Container logs: " + postgis.getLogs());
            throw e;
        }
    }
}

TimescaleDB Extension Errors

Extension installation failure:

try (PostgreSQLContainer<?> timescale = new PostgreSQLContainer<>(
        DockerImageName.parse("timescale/timescaledb:2.14.2-pg16")
            .asCompatibleSubstituteFor("postgres"))) {
    timescale.start();
    
    try (Connection conn = DriverManager.getConnection(
            timescale.getJdbcUrl(),
            timescale.getUsername(),
            timescale.getPassword());
         Statement stmt = conn.createStatement()) {
        
        // Verify TimescaleDB extension
        try (ResultSet rs = stmt.executeQuery(
                "SELECT COUNT(*) FROM pg_available_extensions WHERE name = 'timescaledb'")) {
            rs.next();
            if (rs.getInt(1) == 0) {
                throw new IllegalStateException("TimescaleDB extension not available");
            }
        }
        
        // Enable extension
        stmt.execute("CREATE EXTENSION IF NOT EXISTS timescaledb");
        
        // Verify extension is enabled
        try (ResultSet rs = stmt.executeQuery(
                "SELECT extversion FROM pg_extension WHERE extname = 'timescaledb'")) {
            if (!rs.next()) {
                throw new IllegalStateException("TimescaleDB extension not enabled");
            }
        }
    }
}

PgVector Extension Errors

Vector extension not available:

try (PostgreSQLContainer<?> pgvector = new PostgreSQLContainer<>("pgvector/pgvector:pg16")) {
    pgvector.start();
    
    try (Connection conn = DriverManager.getConnection(
            pgvector.getJdbcUrl(),
            pgvector.getUsername(),
            pgvector.getPassword());
         Statement stmt = conn.createStatement()) {
        
        // Verify vector extension
        try (ResultSet rs = stmt.executeQuery(
                "SELECT COUNT(*) FROM pg_available_extensions WHERE name = 'vector'")) {
            rs.next();
            if (rs.getInt(1) == 0) {
                throw new IllegalStateException("Vector extension not available in image");
            }
        }
        
        // Enable extension
        stmt.execute("CREATE EXTENSION IF NOT EXISTS vector");
        
        // Verify vector type is available
        try (ResultSet rs = stmt.executeQuery(
                "SELECT COUNT(*) FROM pg_type WHERE typname = 'vector'")) {
            rs.next();
            if (rs.getInt(1) == 0) {
                throw new IllegalStateException("Vector type not available");
            }
        }
    }
}

Image Compatibility Issues

Handling incompatible images:

// Use asCompatibleSubstituteFor for variant images
DockerImageName postgisImage = DockerImageName
    .parse("postgis/postgis:16-3.4")
    .asCompatibleSubstituteFor("postgres");

try (PostgreSQLContainer<?> postgis = new PostgreSQLContainer<>(postgisImage)) {
    postgis.start();
    // Use container
} catch (IllegalArgumentException e) {
    // Image compatibility issue
    System.err.println("Image compatibility error: " + e.getMessage());
    throw e;
}

Verifying Variant Functionality

PostGIS verification:

try (PostgreSQLContainer<?> postgis = new PostgreSQLContainer<>(
        DockerImageName.parse("postgis/postgis:16-3.4")
            .asCompatibleSubstituteFor("postgres"))) {
    postgis.start();
    
    try (Connection conn = DriverManager.getConnection(
            postgis.getJdbcUrl(),
            postgis.getUsername(),
            postgis.getPassword());
         Statement stmt = conn.createStatement()) {
        
        stmt.execute("CREATE EXTENSION IF NOT EXISTS postgis");
        
        // Verify PostGIS functions are available
        try (ResultSet rs = stmt.executeQuery(
                "SELECT COUNT(*) FROM pg_proc WHERE proname LIKE 'st_%'")) {
            rs.next();
            int functionCount = rs.getInt(1);
            assert functionCount > 0 : "PostGIS functions should be available";
        }
    }
}

TimescaleDB verification:

try (PostgreSQLContainer<?> timescale = new PostgreSQLContainer<>(
        DockerImageName.parse("timescale/timescaledb:2.14.2-pg16")
            .asCompatibleSubstituteFor("postgres"))) {
    timescale.start();
    
    try (Connection conn = DriverManager.getConnection(
            timescale.getJdbcUrl(),
            timescale.getUsername(),
            timescale.getPassword());
         Statement stmt = conn.createStatement()) {
        
        stmt.execute("CREATE EXTENSION IF NOT EXISTS timescaledb");
        
        // Verify TimescaleDB functions are available
        try (ResultSet rs = stmt.executeQuery(
                "SELECT COUNT(*) FROM pg_proc WHERE proname LIKE 'create_hypertable'")) {
            rs.next();
            assert rs.getInt(1) > 0 : "TimescaleDB functions should be available";
        }
    }
}

PgVector verification:

try (PostgreSQLContainer<?> pgvector = new PostgreSQLContainer<>("pgvector/pgvector:pg16")) {
    pgvector.start();
    
    try (Connection conn = DriverManager.getConnection(
            pgvector.getJdbcUrl(),
            pgvector.getUsername(),
            pgvector.getPassword());
         Statement stmt = conn.createStatement()) {
        
        stmt.execute("CREATE EXTENSION IF NOT EXISTS vector");
        
        // Verify vector operators are available
        try (ResultSet rs = stmt.executeQuery(
                "SELECT COUNT(*) FROM pg_operator WHERE oprname IN ('<->', '<=>', '<#>')")) {
            rs.next();
            assert rs.getInt(1) >= 3 : "Vector operators should be available";
        }
    }
}