From d3a7be7a3b9710856d451a2868785c039889617d Mon Sep 17 00:00:00 2001 From: Jakub Skoczen Date: Mon, 2 Feb 2026 22:53:39 +0100 Subject: [PATCH 1/3] Use CQL builder --- broker/go.mod | 2 +- broker/go.sum | 2 + broker/oapi/open-api.yaml | 1 - broker/patron_request/api/api-handler.go | 70 +++++++++++++++++------- broker/patron_request/db/prcql.go | 12 ++++ 5 files changed, 66 insertions(+), 21 deletions(-) diff --git a/broker/go.mod b/broker/go.mod index 74a4ad89..4654b13d 100644 --- a/broker/go.mod +++ b/broker/go.mod @@ -23,7 +23,7 @@ require ( github.com/dustin/go-humanize v1.0.1 github.com/golang-migrate/migrate/v4 v4.19.1 github.com/google/uuid v1.6.0 - github.com/indexdata/cql-go v1.0.1-0.20250722084932-84f3837d6030 + github.com/indexdata/cql-go v1.0.1-0.20260202173855-d9fe780396b2 github.com/indexdata/go-utils v0.0.0-20250210100229-d30dbd51df72 github.com/indexdata/xsd2goxsl v1.3.0 github.com/jackc/pgerrcode v0.0.0-20240316143900-6e2875d9b438 diff --git a/broker/go.sum b/broker/go.sum index 35c9c567..d2a81891 100644 --- a/broker/go.sum +++ b/broker/go.sum @@ -74,6 +74,8 @@ github.com/grpc-ecosystem/grpc-gateway/v2 v2.23.0/go.mod h1:igFoXX2ELCW06bol23DW github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/indexdata/cql-go v1.0.1-0.20250722084932-84f3837d6030 h1:RP9TZZTGoBo2Jx04FEWaRmNhCX/Fk8VR04tX0FMRzik= github.com/indexdata/cql-go v1.0.1-0.20250722084932-84f3837d6030/go.mod h1:zmSHcE8JyK94EWZrV7VyjLr2QfRoj+EeEOttl9wm64U= +github.com/indexdata/cql-go v1.0.1-0.20260202173855-d9fe780396b2 h1:HYBpSNIr26sFXyOfq4pMGk13ygEezb1UA4gtrt9lsm0= +github.com/indexdata/cql-go v1.0.1-0.20260202173855-d9fe780396b2/go.mod h1:zmSHcE8JyK94EWZrV7VyjLr2QfRoj+EeEOttl9wm64U= github.com/indexdata/go-utils v0.0.0-20250210100229-d30dbd51df72 h1:/ssOlpKK1EOeWMCOUrxCOCZP+0Z1LD58Xg1nrU01TAQ= github.com/indexdata/go-utils v0.0.0-20250210100229-d30dbd51df72/go.mod h1:0sW6Szxv8GNU3LBtK6mgBKDEUnlovPfghiG9xi+i0R8= github.com/indexdata/xsd2goxsl v1.3.0 h1:LZGBORHnO6olHBtvc6hQEefymdRuiM77FgtW4pCek7g= diff --git a/broker/oapi/open-api.yaml b/broker/oapi/open-api.yaml index 77b288ba..e1f74b88 100644 --- a/broker/oapi/open-api.yaml +++ b/broker/oapi/open-api.yaml @@ -1084,4 +1084,3 @@ paths: application/json: schema: $ref: '#/components/schemas/SseResult' - diff --git a/broker/patron_request/api/api-handler.go b/broker/patron_request/api/api-handler.go index dc2730c9..4daba9c1 100644 --- a/broker/patron_request/api/api-handler.go +++ b/broker/patron_request/api/api-handler.go @@ -10,6 +10,7 @@ import ( "time" "github.com/google/uuid" + "github.com/indexdata/cql-go/cqlbuilder" "github.com/indexdata/crosslink/broker/api" "github.com/indexdata/crosslink/broker/common" "github.com/indexdata/crosslink/broker/events" @@ -60,9 +61,40 @@ func (a *PatronRequestApiHandler) GetPatronRequests(w http.ResponseWriter, r *ht if params.Offset != nil { offset = *params.Offset } - - updatedCql := addAccessRestrictions(params.Cql, params.Side, symbol) - prs, count, err := a.prRepo.ListPatronRequests(ctx, pr_db.ListPatronRequestsParams{Limit: limit, Offset: offset}, &updatedCql) + var qb *cqlbuilder.QueryBuilder + if params.Cql == nil { + qb = cqlbuilder.NewQuery() + qb.Search("cql.allRecords").Term("1") + } else { + qb, err = cqlbuilder.NewQueryFromString(*params.Cql) + if err != nil { + addBadRequestError(ctx, w, err) + return + } + } + switch params.Side { + case string(prservice.SideBorrowing), string(prservice.SideLending): + _, err = qb.And().Search("side").Term(params.Side).Build() + if err != nil { + addBadRequestError(ctx, w, err) + return + } + default: + addBadRequestError(ctx, w, errors.New("invalid side parameter, valid values are 'borrowing' and 'lending'")) + return + } + qb, err = addOwnerRestriction(qb, symbol) + if err != nil { + addBadRequestError(ctx, w, err) + return + } + cql, err := qb.Build() + if err != nil { + addBadRequestError(ctx, w, err) + return + } + cqlStr := cql.String() + prs, count, err := a.prRepo.ListPatronRequests(ctx, pr_db.ListPatronRequestsParams{Limit: limit, Offset: offset}, &cqlStr) if err != nil && !errors.Is(err, pgx.ErrNoRows) { //DB error addInternalError(ctx, w, err) return @@ -87,18 +119,18 @@ func (a *PatronRequestApiHandler) GetPatronRequests(w http.ResponseWriter, r *ht writeJsonResponse(w, resp) } -func addAccessRestrictions(cql *string, side string, symbol string) string { - var restrict string - if side == string(prservice.SideLending) { - restrict = "side = " + string(prservice.SideLending) + " AND supplier_symbol = " + symbol - } else { - restrict = "side = " + string(prservice.SideBorrowing) + " AND requester_symbol = " + symbol - } - if cql == nil { - return restrict - } else { - return *cql + " AND " + restrict - } +func addOwnerRestriction(queryBuilder *cqlbuilder.QueryBuilder, symbol string) (*cqlbuilder.QueryBuilder, error) { + _, err := queryBuilder.And(). + BeginClause(). + Search("side").Term(string(prservice.SideLending)). + And().Search("supplier_symbol").Term(symbol). + EndClause(). + Or(). + BeginClause().Search("side").Term(string(prservice.SideBorrowing)). + And().Search("requester_symbol").Term(symbol). + EndClause(). + Build() + return queryBuilder, err } func (a *PatronRequestApiHandler) PostPatronRequests(w http.ResponseWriter, r *http.Request, params proapi.PostPatronRequestsParams) { @@ -185,15 +217,15 @@ func (a *PatronRequestApiHandler) getPatronRequestById(ctx common.ExtendedContex } return nil, err } - if isOwner(pr, side, symbol) { + if string(pr.Side) == side && isOwner(pr, symbol) { return &pr, nil } return nil, nil } -func isOwner(pr pr_db.PatronRequest, side string, symbol string) bool { - return string(pr.Side) == side && ((side == string(prservice.SideBorrowing) && pr.RequesterSymbol.String == symbol) || - (side == string(prservice.SideLending) && pr.SupplierSymbol.String == symbol)) +func isOwner(pr pr_db.PatronRequest, symbol string) bool { + return (string(pr.Side) == string(prservice.SideBorrowing) && pr.RequesterSymbol.String == symbol) || + (string(pr.Side) == string(prservice.SideLending) && pr.SupplierSymbol.String == symbol) } func (a *PatronRequestApiHandler) GetPatronRequestsId(w http.ResponseWriter, r *http.Request, id string, params proapi.GetPatronRequestsIdParams) { diff --git a/broker/patron_request/db/prcql.go b/broker/patron_request/db/prcql.go index d37a6070..3d7e194f 100644 --- a/broker/patron_request/db/prcql.go +++ b/broker/patron_request/db/prcql.go @@ -9,9 +9,21 @@ import ( "github.com/indexdata/cql-go/pgcql" ) +type FieldAllRecords struct{} + +func (f *FieldAllRecords) GetColumn() string { return "" } +func (f *FieldAllRecords) SetColumn(column string) {} +func (f *FieldAllRecords) Generate(sc cql.SearchClause, queryArgumentIndex int) (string, []any, error) { + // Accept standard cql.allRecords = 1 (ignore term/relation). + return "TRUE", nil, nil +} + func handlePatronRequestsQuery(cqlString string, noBaseArgs int) (pgcql.Query, error) { def := pgcql.NewPgDefinition() + fa := &FieldAllRecords{} + def.AddField("cql.allRecords", fa) + f := pgcql.NewFieldString().WithExact() def.AddField("state", f) From f62b17119a3f4c4766f0a4d3f91b1f32c26dd4c2 Mon Sep 17 00:00:00 2001 From: Jakub Skoczen Date: Mon, 2 Feb 2026 23:20:26 +0100 Subject: [PATCH 2/3] Use side as a hint to simplify query --- broker/patron_request/api/api-handler.go | 40 +++++++++++++++++------- 1 file changed, 28 insertions(+), 12 deletions(-) diff --git a/broker/patron_request/api/api-handler.go b/broker/patron_request/api/api-handler.go index 4daba9c1..04e91179 100644 --- a/broker/patron_request/api/api-handler.go +++ b/broker/patron_request/api/api-handler.go @@ -72,8 +72,10 @@ func (a *PatronRequestApiHandler) GetPatronRequests(w http.ResponseWriter, r *ht return } } + var side pr_db.PatronRequestSide switch params.Side { case string(prservice.SideBorrowing), string(prservice.SideLending): + side = pr_db.PatronRequestSide(params.Side) _, err = qb.And().Search("side").Term(params.Side).Build() if err != nil { addBadRequestError(ctx, w, err) @@ -83,7 +85,7 @@ func (a *PatronRequestApiHandler) GetPatronRequests(w http.ResponseWriter, r *ht addBadRequestError(ctx, w, errors.New("invalid side parameter, valid values are 'borrowing' and 'lending'")) return } - qb, err = addOwnerRestriction(qb, symbol) + qb, err = addOwnerRestriction(qb, symbol, side) if err != nil { addBadRequestError(ctx, w, err) return @@ -119,17 +121,31 @@ func (a *PatronRequestApiHandler) GetPatronRequests(w http.ResponseWriter, r *ht writeJsonResponse(w, resp) } -func addOwnerRestriction(queryBuilder *cqlbuilder.QueryBuilder, symbol string) (*cqlbuilder.QueryBuilder, error) { - _, err := queryBuilder.And(). - BeginClause(). - Search("side").Term(string(prservice.SideLending)). - And().Search("supplier_symbol").Term(symbol). - EndClause(). - Or(). - BeginClause().Search("side").Term(string(prservice.SideBorrowing)). - And().Search("requester_symbol").Term(symbol). - EndClause(). - Build() +func addOwnerRestriction(queryBuilder *cqlbuilder.QueryBuilder, symbol string, side pr_db.PatronRequestSide) (*cqlbuilder.QueryBuilder, error) { + var err error + switch side { + case prservice.SideLending: + _, err = queryBuilder.And(). + Search("side").Term(string(prservice.SideLending)). + And().Search("supplier_symbol").Term(symbol). + Build() + case prservice.SideBorrowing: + _, err = queryBuilder.And(). + Search("side").Term(string(prservice.SideBorrowing)). + And().Search("requester_symbol").Term(symbol). + Build() + default: + _, err = queryBuilder.And(). + BeginClause(). + Search("side").Term(string(prservice.SideLending)). + And().Search("supplier_symbol").Term(symbol). + EndClause(). + Or(). + BeginClause().Search("side").Term(string(prservice.SideBorrowing)). + And().Search("requester_symbol").Term(symbol). + EndClause(). + Build() + } return queryBuilder, err } From 3610d4518361765b613738553acfe71ec34ec45a Mon Sep 17 00:00:00 2001 From: Jakub Skoczen Date: Tue, 3 Feb 2026 08:20:20 +0100 Subject: [PATCH 3/3] Allow empty or invalid side param --- broker/patron_request/api/api-handler.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/broker/patron_request/api/api-handler.go b/broker/patron_request/api/api-handler.go index 04e91179..aa638b00 100644 --- a/broker/patron_request/api/api-handler.go +++ b/broker/patron_request/api/api-handler.go @@ -73,17 +73,13 @@ func (a *PatronRequestApiHandler) GetPatronRequests(w http.ResponseWriter, r *ht } } var side pr_db.PatronRequestSide - switch params.Side { - case string(prservice.SideBorrowing), string(prservice.SideLending): + if isSideParamValid(params.Side) { side = pr_db.PatronRequestSide(params.Side) _, err = qb.And().Search("side").Term(params.Side).Build() if err != nil { addBadRequestError(ctx, w, err) return } - default: - addBadRequestError(ctx, w, errors.New("invalid side parameter, valid values are 'borrowing' and 'lending'")) - return } qb, err = addOwnerRestriction(qb, symbol, side) if err != nil { @@ -233,12 +229,16 @@ func (a *PatronRequestApiHandler) getPatronRequestById(ctx common.ExtendedContex } return nil, err } - if string(pr.Side) == side && isOwner(pr, symbol) { + if isOwner(pr, symbol) && (!isSideParamValid(side) || string(pr.Side) == side) { return &pr, nil } return nil, nil } +func isSideParamValid(side string) bool { + return side == string(prservice.SideBorrowing) || side == string(prservice.SideLending) +} + func isOwner(pr pr_db.PatronRequest, symbol string) bool { return (string(pr.Side) == string(prservice.SideBorrowing) && pr.RequesterSymbol.String == symbol) || (string(pr.Side) == string(prservice.SideLending) && pr.SupplierSymbol.String == symbol)