jmoiron / sqlx

general purpose extensions to golang's database/sql
http://jmoiron.github.io/sqlx/
MIT License
16.3k stars 1.09k forks source link

why? #943

Open complexusprada opened 2 months ago

complexusprada commented 2 months ago

I have 2 codes that produce same sql statement: 1. This code works correctly.

func applyFilter(filter Filter, data map[string]interface{}, buf *bytes.Buffer) {
    var wc []string

    arrjoin := ` 
        LEFT OUTER JOIN 
            caldera_operations__arrangements 
        ON 
            caldera_operations.id = caldera_operations__arrangements.operation_id `
    endjoin := ` 
        LEFT OUTER JOIN 
            endpoints 
        ON 
            endpoints.id = caldera_operations__arrangements.endpoint_id `

    if filter.Name != nil {
        data["name"] = fmt.Sprintf("%%%s%%", *filter.Name)
        wc = append(wc, "LOWER(name) LIKE LOWER(:name)")
    }

    if filter.ArrangementID != nil {
        data["arrangement_id"] = *filter.ArrangementID
        // arrjoin = " INNER JOIN caldera_operations__arrangements ON caldera_operations.id = caldera_operations__arrangements.operation_id "
        wc = append(wc, "caldera_operations__arrangements.arrangement_id = :arrangement_id")
    }

    if filter.EndpointID != nil {
        data["endpoint_id"] = *filter.EndpointID
        // endjoin = " INNER JOIN caldera_operations__arrangements ON caldera_operations.id = caldera_operations__arrangements.operation_id " 
        wc = append(wc, "caldera_operations__arrangements.endpoint_id = :endpoint_id")
    }

    buf.WriteString(arrjoin)
    buf.WriteString(endjoin)

    if len(wc) > 0 {
        buf.WriteString(" WHERE ")
        buf.WriteString(strings.Join(wc, " AND "))
    }
}
func (s Store) Operations(ctx context.Context, page, limit int, filter Filter) ([]Operation, error) {
    var operations []Operation
    data := map[string]interface{}{
        "offset": (page - 1) * limit,
        "limit":  limit,
    }

    // const q = `
 //        SELECT  
 //            caldera_operations.id,
 //            caldera_operations.name,
 //            caldera_operations.description,
 //            caldera_operations.adversary_profile_id,
 //            coalesce(caldera_operations__arrangements.endpoint_id, 0) AS endpoint_id,
 //            coalesce(caldera_operations__arrangements.arrangement_id, 0) AS arrangement_id,
 //            coalesce(endpoints.name, '') AS endpoint_hostname,
 //            caldera_operations.created_at,
 //            caldera_operations.updated_at 
 //        FROM 
 //            caldera_operations
 //        LEFT OUTER JOIN 
 //            caldera_operations__arrangements 
 //        ON 
 //            caldera_operations.id = caldera_operations__arrangements.operation_id
 //        LEFT OUTER JOIN 
 //            endpoints 
 //        ON 
 //            endpoints.id = caldera_operations__arrangements.endpoint_id `

    const q = `
        SELECT  
            caldera_operations.id,
            caldera_operations.name,
            caldera_operations.description,
            caldera_operations.adversary_profile_id,
            coalesce(caldera_operations__arrangements.endpoint_id, 0) AS endpoint_id,
            coalesce(caldera_operations__arrangements.arrangement_id, 0) AS arrangement_id,
            coalesce(endpoints.name, '') AS endpoint_hostname,
            caldera_operations.created_at,
            caldera_operations.updated_at 
        FROM 
            caldera_operations`

    buf := bytes.NewBufferString(q)

    applyFilter(filter, data, buf)

    buf.WriteString(" ORDER BY created_at DESC")
    buf.WriteString(" OFFSET :offset ROWS FETCH NEXT :limit ROWS ONLY")

    fmt.Println(buf.String())

    rows, err := sqlx.NamedQueryContext(ctx, s.db, buf.String(), data)
    if err != nil {
        return nil, base.DBError(err)
    }

    for rows.Next() {
        var a Operation
        err := rows.StructScan(&a)
        if err != nil {
            return nil, base.DBError(err)
        }

        operations = append(operations, a)
    }

    return operations, nil
}

the code produces the following sql statement.

        SELECT
            caldera_operations.id,
            caldera_operations.name,
            caldera_operations.description,
            caldera_operations.adversary_profile_id,
            coalesce(caldera_operations__arrangements.endpoint_id, 0) AS endpoint_id,
            coalesce(caldera_operations__arrangements.arrangement_id, 0) AS arrangement_id,
            coalesce(endpoints.name, '') AS endpoint_hostname,
            caldera_operations.created_at,
            caldera_operations.updated_at
        FROM
            caldera_operations
        LEFT OUTER JOIN
            caldera_operations__arrangements
        ON
            caldera_operations.id = caldera_operations__arrangements.operation_id
        LEFT OUTER JOIN
            endpoints
        ON
            endpoints.id = caldera_operations__arrangements.endpoint_id  WHERE caldera_operations__arrangements.arrangement_id = :arrangement_id ORDER BY created_at DESC OFFSET :offset ROWS FETCH NEXT :limit ROWS ONLY

The second one which also produces the same sql statement for some reason doesn't work. 2.

    var operations []Operation
    data := map[string]interface{}{
        "offset": (page - 1) * limit,
        "limit":  limit,
    }

    const q = `
        SELECT  
            caldera_operations.id,
            caldera_operations.name,
            caldera_operations.description,
            caldera_operations.adversary_profile_id,
            coalesce(caldera_operations__arrangements.endpoint_id, 0) AS endpoint_id,
            coalesce(caldera_operations__arrangements.arrangement_id, 0) AS arrangement_id,
            coalesce(endpoints.name, '') AS endpoint_hostname,
            caldera_operations.created_at,
            caldera_operations.updated_at 
        FROM 
            caldera_operations
        LEFT OUTER JOIN 
            caldera_operations__arrangements 
        ON 
            caldera_operations.id = caldera_operations__arrangements.operation_id
        LEFT OUTER JOIN 
            endpoints 
        ON 
            endpoints.id = caldera_operations__arrangements.endpoint_id `

    // const q = `
 //        SELECT  
 //            caldera_operations.id,
 //            caldera_operations.name,
 //            caldera_operations.description,
 //            caldera_operations.adversary_profile_id,
 //            coalesce(caldera_operations__arrangements.endpoint_id, 0) AS endpoint_id,
 //            coalesce(caldera_operations__arrangements.arrangement_id, 0) AS arrangement_id,
 //            coalesce(endpoints.name, '') AS endpoint_hostname,
 //            caldera_operations.created_at,
 //            caldera_operations.updated_at 
 //        FROM 
 //            caldera_operations`

    buf := bytes.NewBufferString(q)

    applyFilter(filter, data, buf)

    buf.WriteString(" ORDER BY created_at DESC")
    buf.WriteString(" OFFSET :offset ROWS FETCH NEXT :limit ROWS ONLY")

    fmt.Println(buf.String())

    rows, err := sqlx.NamedQueryContext(ctx, s.db, buf.String(), data)
    if err != nil {
        return nil, base.DBError(err)
    }

    for rows.Next() {
        var a Operation
        err := rows.StructScan(&a)
        if err != nil {
            return nil, base.DBError(err)
        }

        operations = append(operations, a)
    }

    return operations, nil
}
func (s Store) Operations(ctx context.Context, page, limit int, filter Filter) ([]Operation, error) {
    var operations []Operation
    data := map[string]interface{}{
        "offset": (page - 1) * limit,
        "limit":  limit,
    }

    const q = `
        SELECT  
            caldera_operations.id,
            caldera_operations.name,
            caldera_operations.description,
            caldera_operations.adversary_profile_id,
            coalesce(caldera_operations__arrangements.endpoint_id, 0) AS endpoint_id,
            coalesce(caldera_operations__arrangements.arrangement_id, 0) AS arrangement_id,
            coalesce(endpoints.name, '') AS endpoint_hostname,
            caldera_operations.created_at,
            caldera_operations.updated_at 
        FROM 
            caldera_operations
        LEFT OUTER JOIN 
            caldera_operations__arrangements 
        ON 
            caldera_operations.id = caldera_operations__arrangements.operation_id
        LEFT OUTER JOIN 
            endpoints 
        ON 
            endpoints.id = caldera_operations__arrangements.endpoint_id `

    // const q = `
 //        SELECT  
 //            caldera_operations.id,
 //            caldera_operations.name,
 //            caldera_operations.description,
 //            caldera_operations.adversary_profile_id,
 //            coalesce(caldera_operations__arrangements.endpoint_id, 0) AS endpoint_id,
 //            coalesce(caldera_operations__arrangements.arrangement_id, 0) AS arrangement_id,
 //            coalesce(endpoints.name, '') AS endpoint_hostname,
 //            caldera_operations.created_at,
 //            caldera_operations.updated_at 
 //        FROM 
 //            caldera_operations`

    buf := bytes.NewBufferString(q)

    applyFilter(filter, data, buf)

    buf.WriteString(" ORDER BY created_at DESC")
    buf.WriteString(" OFFSET :offset ROWS FETCH NEXT :limit ROWS ONLY")

    fmt.Println(buf.String())

    rows, err := sqlx.NamedQueryContext(ctx, s.db, buf.String(), data)
    if err != nil {
        return nil, base.DBError(err)
    }

    for rows.Next() {
        var a Operation
        err := rows.StructScan(&a)
        if err != nil {
            return nil, base.DBError(err)
        }

        operations = append(operations, a)
    }

    return operations, nil
}

it produces the following sql statement:

        SELECT
            caldera_operations.id,
            caldera_operations.name,
            caldera_operations.description,
            caldera_operations.adversary_profile_id,
            coalesce(caldera_operations__arrangements.endpoint_id, 0) AS endpoint_id,
            coalesce(caldera_operations__arrangements.arrangement_id, 0) AS arrangement_id,
            coalesce(endpoints.name, '') AS endpoint_hostname,
            caldera_operations.created_at,
            caldera_operations.updated_at
        FROM
            caldera_operations
        LEFT OUTER JOIN
            caldera_operations__arrangements
        ON
            caldera_operations.id = caldera_operations__arrangements.operation_id
        LEFT OUTER JOIN
            endpoints
        ON
            endpoints.id = caldera_operations__arrangements.endpoint_id  WHERE caldera_operations__arrangements.arrangement_id = :arrangement_id ORDER BY created_at DESC OFFSET :offset ROWS FETCH NEXT :limit ROWS ONLY

So only difference between these codes is that, I use buf.WriteString to include joins. The second one gives the following error: {"time":"2024-09-10T14:36:31.523814521+05:00","level":"ERROR","msg":"ERROR: missing FROM-clause entry for table \"caldera_operations__arrangements\" (SQLSTATE 42P01)"}

Can someone explain WTf is happening o_o