Back to Kysely

Execution flow

site/docs/execution.mdx

0.28.177.4 KB
Original Source

The following page gives a simplified overview of Kysely's execution flow, from query building to querying the database. It is a nice introduction for anyone looking to understand how Kysely works under the hood. Knowing what your dependencies do and don't do is always a good idea!

This breakdown explains the journey from a type-safe method call in your application to receiving results from the database, as depicted in the diagram.

  1. Immutable query building

    The process starts in your App. You interact with the QueryBuilder by calling its methods (selectFrom, where, etc.). Each call returns a new QueryBuilder instance containing an updated, immutable QueryAST (Abstract Syntax Tree), which is the internal representation of your SQL query.

    mermaid
    sequenceDiagram
        actor Y as App
        participant QB as QueryBuilder
    
        activate Y
    
        loop more to build
            Y->>+QB: method(type-safe inputs)
            QB->>QB: QueryAST
            QB-->>Y: new QueryBuilder
        end
    
        deactivate QB
        deactivate Y
    
  2. Initiating execution

    When you chain the final .execute() call, the QueryBuilder begins a multi-step execution process, commanding the QueryExecutor to perform distinct tasks.

    mermaid
    sequenceDiagram
        autonumber 4
    
        actor Y as App
        participant QB as QueryBuilder
    
        activate Y
    
        Y->>+QB: execute()
    
        deactivate QB
        deactivate Y
    
  3. Query Transformation

    First, the QueryBuilder instructs the QueryExecutor to process the QueryAST. The QueryExecutor iterates through all registered plugins, calling transformQuery on each. This allows plugins to modify the query structure before compilation. The final, transformed QueryAST is returned to the QueryBuilder.

    mermaid
    sequenceDiagram
        autonumber 5
    
        participant QB as QueryBuilder
        participant QE as QueryExecutor
        participant P as Plugin
    
        activate QB
    
        QB->>+QE: transformQuery(QueryAST)
    
            loop all plugins
                QE->>+P: transformQuery(QueryAST)
                P-->>-QE: Transformed QueryAST
            end
    
        QE-->>-QB: Transformed QueryAST
    
        deactivate QB
    
  4. Query Compilation

    Next, the QueryBuilder tells the QueryExecutor to compile the transformed AST. The QueryExecutor delegates this to the Dialect-specific QueryCompiler. The compiler traverses the AST and produces a CompiledQuery object (containing the final SQL string and parameters). This CompiledQuery is then returned to the QueryBuilder.

    mermaid
    sequenceDiagram
        autonumber 9
    
        participant QB as QueryBuilder
        participant QE as QueryExecutor
    
        box Dialect
            participant QC as QueryCompiler
        end
    
        activate QB
    
        QB->>+QE: compileQuery(QueryAST)
    
            QE->>+QC: compileQuery(QueryAST)
            QC-->>-QE: CompiledQuery
    
        QE-->>-QB: CompiledQuery
    
        deactivate QB
    
  5. Execution & Connection Handling

    The QueryBuilder now asks the QueryExecutor to execute the CompiledQuery.

    • The QueryExecutor requests a connection from Kysely's Driver.
    • The Driver's job is to abstract away vendor-specific details. It communicates with the actual third-party DatabaseDriver — for example, the pg or mysql2 npm package — to get a connection from its pool.
    • A DatabaseConnection object, which wraps the native connection, is returned to the QueryExecutor.
    mermaid
    sequenceDiagram
        autonumber 13
    
        participant QB as QueryBuilder
        participant QE as QueryExecutor
    
        box Dialect
            participant D as Driver
        end
    
        actor DD as DatabaseDriver
    
        activate QB
    
        QB->>+QE: executeQuery(CompiledQuery)
    
            QE->>+D: acquireConnection()
    
                opt differs per driver
                    D->>+DD:
                    DD-->>-D: connection
                end
    
            D-->>-QE: new DatabaseConnection
    
        deactivate QE
        deactivate QB
    
  6. Database Query

    The QueryExecutor passes the CompiledQuery to the DatabaseConnection object, which executes it. The DatabaseConnection uses the underlying DatabaseDriver to send the SQL and parameters to the database for execution. The DatabaseDriver sends the raw results back. The DatabaseConnection standardizes these into a QueryResult object and returns it to the QueryExecutor. Immediately after, the connection is released back to the pool.

    mermaid
    sequenceDiagram
        autonumber 18
    
        participant QE as QueryExecutor
    
        box Dialect
            participant DC as DatabaseConnection
        end
    
        actor DD as DatabaseDriver
    
        activate QE
    
        QE->>+DC: executeQuery(CompiledQuery)
    
            DC->>+DD: SQL + Params
            DD-->>-DC: Raw Results
    
        DC-->>-QE: QueryResult
    
        deactivate QE
    
  7. Result Transformation

    The QueryResult is then passed through the plugin system again. The QueryExecutor calls the transformResult method on each plugin, allowing for final modifications to the results before they are returned to the App.

    mermaid
    sequenceDiagram
        autonumber 22
    
        participant QE as QueryExecutor
        participant P as Plugin
    
        activate QE
    
        loop all plugins
            QE->>+P: transformResults(QueryResult)
            P-->>-QE: Transformed QueryResult
        end
    
        deactivate QE
    
  8. Returning to the App

    The final, transformed QueryResult is passed up from the QueryExecutor to the QueryBuilder. The QueryBuilder then resolves the promise from the initial .execute() call, delivering the final, typed results to your App.

mermaid
sequenceDiagram
    actor Y as App
    participant QB as QueryBuilder
    participant QE as QueryExecutor
    participant P as Plugin

    box Dialect
        participant QC as QueryCompiler
        participant D as Driver
        participant DC as DatabaseConnection
    end

    actor DD as DatabaseDriver

    activate Y

    loop more to build
        Y->>+QB: method(type-safe inputs)
        QB->>QB: QueryAST
        QB-->>Y: new QueryBuilder
    end

    Y->>QB: execute()

        QB->>+QE: transformQuery(QueryAST)

            loop all plugins
                QE->>+P: transformQuery(QueryAST)
                P-->>-QE: Transformed QueryAST
            end

        QE-->>-QB: Transformed QueryAST

        QB->>+QE: compileQuery(QueryAST)

            QE->>+QC: compileQuery(QueryAST)
            QC-->>-QE: CompiledQuery

        QE-->>-QB: CompiledQuery

        QB->>+QE: executeQuery(CompiledQuery)

            QE->>+D: acquireConnection()

                opt differs per driver
                    D->>+DD:
                    DD-->>-D: connection
                end

            D-->>-QE: new DatabaseConnection

            QE->>+DC: executeQuery(CompiledQuery)

                DC->>+DD: SQL + Params
                DD-->>-DC: Raw Results

            DC-->>-QE: QueryResult

            loop all plugins
                QE->>+P: transformResults(QueryResult)
                P-->>-QE: Transformed QueryResult
            end

        QE-->>-QB: Query Results

    QB-->>-Y: Query Results

    deactivate Y