You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficcontrol.apache.org by oc...@apache.org on 2019/08/07 16:24:16 UTC

[trafficcontrol] branch master updated: Add pagination support for TO API endpoints that use BuildWhereAndOrderBy (#3701)

This is an automated email from the ASF dual-hosted git repository.

ocket8888 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficcontrol.git


The following commit(s) were added to refs/heads/master by this push:
     new 0747ef6  Add pagination support for TO API endpoints that use BuildWhereAndOrderBy (#3701)
0747ef6 is described below

commit 0747ef6880d77cb15e68f852db31f4f8a39ad084
Author: Alex Luckerman <35...@users.noreply.github.com>
AuthorDate: Wed Aug 7 10:24:11 2019 -0600

    Add pagination support for TO API endpoints that use BuildWhereAndOrderBy (#3701)
    
    * Add limit param to endpoints that support orderby
    
    * Add offset param support for pagination
    
    * Remove accidentally included sortOrder param code
    
    * Update CHANGELOG.md
    
    * Update db_helpers_test for pagination
    
    * Add verification for pagination query parameters
    
    * Add TO API test for pagination query params
    
    * Update CHANGELOG.md
    
    * Use t.Fatalf
---
 CHANGELOG.md                                       |  1 +
 traffic_ops/testing/api/v14/origins_test.go        | 34 +++++++++++++++++
 traffic_ops/traffic_ops_golang/api/generic_crud.go |  4 +-
 .../traffic_ops_golang/cachegroup/cachegroups.go   |  4 +-
 .../traffic_ops_golang/dbhelpers/db_helpers.go     | 44 ++++++++++++++++++++--
 .../dbhelpers/db_helpers_test.go                   | 11 ++++--
 .../deliveryservice/deliveryservices.go            |  4 +-
 .../deliveryservice/request/requests.go            |  4 +-
 .../deliveryservice/servers/servers.go             |  4 +-
 traffic_ops/traffic_ops_golang/hwinfo/hwinfo.go    |  4 +-
 traffic_ops/traffic_ops_golang/origin/origins.go   |  4 +-
 .../traffic_ops_golang/parameter/parameters.go     |  4 +-
 traffic_ops/traffic_ops_golang/profile/profiles.go |  4 +-
 traffic_ops/traffic_ops_golang/server/servers.go   |  4 +-
 .../steeringtargets/steeringtargets.go             |  4 +-
 traffic_ops/traffic_ops_golang/user/user.go        |  4 +-
 16 files changed, 107 insertions(+), 31 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 66237ad..ce9f36a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -37,6 +37,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/).
 - Added an API 1.4 endpoint, /api/1.4/user/login/oauth to handle SSO login using OAuth.
 - Added /#!/sso page to Traffic Portal to catch redirects back from OAuth provider and POST token into the API.
 - In Traffic Portal, server table columns can now be rearranged and their visibility toggled on/off as desired by the user. Hidden table columns are excluded from the table search. These settings are persisted in the browser.
+- Added pagination support to some Traffic Ops endpoints via three new query parameters, limit and offset/page
 
 ### Changed
 - Traffic Router, added TLS certificate validation on certificates imported from Traffic Ops
diff --git a/traffic_ops/testing/api/v14/origins_test.go b/traffic_ops/testing/api/v14/origins_test.go
index 03d0425..7f527eb 100644
--- a/traffic_ops/testing/api/v14/origins_test.go
+++ b/traffic_ops/testing/api/v14/origins_test.go
@@ -16,6 +16,7 @@ package v14
 */
 
 import (
+	"reflect"
 	"strings"
 	"testing"
 	"time"
@@ -31,6 +32,7 @@ func TestOrigins(t *testing.T) {
 		GetTestOrigins(t)
 		NotFoundDeleteTest(t)
 		OriginTenancyTest(t)
+		VerifyPaginationSupport(t)
 	})
 }
 
@@ -151,6 +153,38 @@ func OriginTenancyTest(t *testing.T) {
 	}
 }
 
+func VerifyPaginationSupport(t *testing.T) {
+	origins, _, err := TOSession.GetOrigins()
+	if err != nil {
+		t.Fatalf("cannot GET origins: %v\n", err)
+	}
+
+	originsWithLimit, _, err := TOSession.GetOriginsByQueryParams("?limit=1")
+	if !reflect.DeepEqual(origins[:1], originsWithLimit) {
+		t.Errorf("expected GET origins with limit = 1 to return first result")
+	}
+
+	originsWithOffset, _, err := TOSession.GetOriginsByQueryParams("?limit=1&offset=1")
+	if !reflect.DeepEqual(origins[1:2], originsWithOffset) {
+		t.Errorf("expected GET origins with limit = 1, offset = 1 to return second result")
+	}
+
+	originsWithPage, _, err := TOSession.GetOriginsByQueryParams("?limit=1&page=2")
+	if !reflect.DeepEqual(origins[1:2], originsWithPage) {
+		t.Errorf("expected GET origins with limit = 1, page = 2 to return second result")
+	}
+
+	if _, _, err := TOSession.GetOriginsByQueryParams("?limit=0"); !strings.Contains(err.Error(), "must be a positive integer") {
+		t.Errorf("expected GET origins to return an error when limit is not a positive integer: " + err.Error())
+	}
+	if _, _, err := TOSession.GetOriginsByQueryParams("?limit=1&offset=0"); !strings.Contains(err.Error(), "must be a positive integer") {
+		t.Errorf("expected GET origins to return an error when offset is not a positive integer: " + err.Error())
+	}
+	if _, _, err := TOSession.GetOriginsByQueryParams("?limit=1&page=0"); !strings.Contains(err.Error(), "must be a positive integer") {
+		t.Errorf("expected GET origins to return an error when page is not a positive integer: " + err.Error())
+	}
+}
+
 func DeleteTestOrigins(t *testing.T) {
 	for _, origin := range testData.Origins {
 		resp, _, err := TOSession.GetOriginByName(*origin.Name)
diff --git a/traffic_ops/traffic_ops_golang/api/generic_crud.go b/traffic_ops/traffic_ops_golang/api/generic_crud.go
index f059c6f..4f27c80 100644
--- a/traffic_ops/traffic_ops_golang/api/generic_crud.go
+++ b/traffic_ops/traffic_ops_golang/api/generic_crud.go
@@ -86,12 +86,12 @@ func GenericCreate(val GenericCreator) (error, error, int) {
 }
 
 func GenericRead(val GenericReader) ([]interface{}, error, error, int) {
-	where, orderBy, queryValues, errs := dbhelpers.BuildWhereAndOrderBy(val.APIInfo().Params, val.ParamColumns())
+	where, orderBy, pagination, queryValues, errs := dbhelpers.BuildWhereAndOrderByAndPagination(val.APIInfo().Params, val.ParamColumns())
 	if len(errs) > 0 {
 		return nil, util.JoinErrs(errs), nil, http.StatusBadRequest
 	}
 
-	query := val.SelectQuery() + where + orderBy
+	query := val.SelectQuery() + where + orderBy + pagination
 	rows, err := val.APIInfo().Tx.NamedQuery(query, queryValues)
 	if err != nil {
 		return nil, nil, errors.New("querying " + val.GetType() + ": " + err.Error()), http.StatusInternalServerError
diff --git a/traffic_ops/traffic_ops_golang/cachegroup/cachegroups.go b/traffic_ops/traffic_ops_golang/cachegroup/cachegroups.go
index 87b519a..f856e70 100644
--- a/traffic_ops/traffic_ops_golang/cachegroup/cachegroups.go
+++ b/traffic_ops/traffic_ops_golang/cachegroup/cachegroups.go
@@ -388,12 +388,12 @@ func (cg *TOCacheGroup) Read() ([]interface{}, error, error, int) {
 		"shortName": dbhelpers.WhereColumnInfo{"short_name", nil},
 		"type":      dbhelpers.WhereColumnInfo{"cachegroup.type", nil},
 	}
-	where, orderBy, queryValues, errs := dbhelpers.BuildWhereAndOrderBy(cg.ReqInfo.Params, queryParamsToQueryCols)
+	where, orderBy, pagination, queryValues, errs := dbhelpers.BuildWhereAndOrderByAndPagination(cg.ReqInfo.Params, queryParamsToQueryCols)
 	if len(errs) > 0 {
 		return nil, util.JoinErrs(errs), nil, http.StatusBadRequest
 	}
 
-	query := SelectQuery() + where + orderBy
+	query := SelectQuery() + where + orderBy + pagination
 	rows, err := cg.ReqInfo.Tx.NamedQuery(query, queryValues)
 	if err != nil {
 		return nil, nil, errors.New("cachegroup read: querying: " + err.Error()), http.StatusInternalServerError
diff --git a/traffic_ops/traffic_ops_golang/dbhelpers/db_helpers.go b/traffic_ops/traffic_ops_golang/dbhelpers/db_helpers.go
index 97bbc93..dfd6cff 100644
--- a/traffic_ops/traffic_ops_golang/dbhelpers/db_helpers.go
+++ b/traffic_ops/traffic_ops_golang/dbhelpers/db_helpers.go
@@ -23,6 +23,7 @@ import (
 	"database/sql"
 	"errors"
 	"fmt"
+	"strconv"
 	"strings"
 
 	"github.com/apache/trafficcontrol/lib/go-log"
@@ -38,10 +39,13 @@ type WhereColumnInfo struct {
 
 const BaseWhere = "\nWHERE"
 const BaseOrderBy = "\nORDER BY"
+const BaseLimit = "\nLIMIT"
+const BaseOffset = "\nOFFSET"
 
-func BuildWhereAndOrderBy(parameters map[string]string, queryParamsToSQLCols map[string]WhereColumnInfo) (string, string, map[string]interface{}, []error) {
+func BuildWhereAndOrderByAndPagination(parameters map[string]string, queryParamsToSQLCols map[string]WhereColumnInfo) (string, string, string, map[string]interface{}, []error) {
 	whereClause := BaseWhere
 	orderBy := BaseOrderBy
+	paginationClause := BaseLimit
 	var criteria string
 	var queryValues map[string]interface{}
 	var errs []error
@@ -51,7 +55,7 @@ func BuildWhereAndOrderBy(parameters map[string]string, queryParamsToSQLCols map
 		whereClause += " " + criteria
 	}
 	if len(errs) > 0 {
-		return "", "", queryValues, errs
+		return "", "", "", queryValues, errs
 	}
 
 	if orderby, ok := parameters["orderby"]; ok {
@@ -63,14 +67,46 @@ func BuildWhereAndOrderBy(parameters map[string]string, queryParamsToSQLCols map
 			log.Debugln("Incorrect name for orderby: ", orderby)
 		}
 	}
+
+	if limit, exists := parameters["limit"]; exists {
+		// try to convert to int, if it fails the limit parameter is invalid, so return an error
+		limitInt, err := strconv.Atoi(limit)
+		if err != nil || limitInt < 1 {
+			errs = append(errs, errors.New("limit parameter must be a positive integer"))
+			return "", "", "", queryValues, errs
+		}
+		log.Debugln("limit: ", limit)
+		paginationClause += " " + limit
+		if offset, exists := parameters["offset"]; exists {
+			// check that offset is valid
+			offsetInt, err := strconv.Atoi(offset)
+			if err != nil || offsetInt < 1 {
+				errs = append(errs, errors.New("offset parameter must be a positive integer"))
+				return "", "", "", queryValues, errs
+			}
+			paginationClause += BaseOffset + " " + offset
+		} else if page, exists := parameters["page"]; exists {
+			// check that offset is valid
+			page, err := strconv.Atoi(page)
+			if err != nil || page < 1 {
+				errs = append(errs, errors.New("page parameter must be a positive integer"))
+				return "", "", "", queryValues, errs
+			}
+			paginationClause += BaseOffset + " " + strconv.Itoa((page-1)*limitInt)
+		}
+	}
+
 	if whereClause == BaseWhere {
 		whereClause = ""
 	}
 	if orderBy == BaseOrderBy {
 		orderBy = ""
 	}
-	log.Debugf("\n--\n Where: %s \n Order By: %s", whereClause, orderBy)
-	return whereClause, orderBy, queryValues, errs
+	if paginationClause == BaseLimit {
+		paginationClause = ""
+	}
+	log.Debugf("\n--\n Where: %s \n Order By: %s \n Limit+Offset: %s", whereClause, orderBy, paginationClause)
+	return whereClause, orderBy, paginationClause, queryValues, errs
 }
 
 func parseCriteriaAndQueryValues(queryParamsToSQLCols map[string]WhereColumnInfo, parameters map[string]string) (string, map[string]interface{}, []error) {
diff --git a/traffic_ops/traffic_ops_golang/dbhelpers/db_helpers_test.go b/traffic_ops/traffic_ops_golang/dbhelpers/db_helpers_test.go
index 5607dfc..4121073 100644
--- a/traffic_ops/traffic_ops_golang/dbhelpers/db_helpers_test.go
+++ b/traffic_ops/traffic_ops_golang/dbhelpers/db_helpers_test.go
@@ -35,7 +35,7 @@ func stripAllWhitespace(s string) string {
 }
 
 func TestBuildQuery(t *testing.T) {
-	v := map[string]string{"param1": "queryParamv1", "param2": "queryParamv2"}
+	v := map[string]string{"param1": "queryParamv1", "param2": "queryParamv2", "limit": "20", "offset": "10"}
 
 	selectStmt := `SELECT
 	t.col1,
@@ -48,10 +48,15 @@ FROM table t
 		"param1": WhereColumnInfo{"t.col1", nil},
 		"param2": WhereColumnInfo{"t.col2", nil},
 	}
-	where, orderBy, queryValues, _ := BuildWhereAndOrderBy(v, queryParamsToSQLCols)
-	query := selectStmt + where + orderBy
+	where, orderBy, pagination, queryValues, _ := BuildWhereAndOrderByAndPagination(v, queryParamsToSQLCols)
+	query := selectStmt + where + orderBy + pagination
 	actualQuery := stripAllWhitespace(query)
 
+	expectedPagination := "\nLIMIT " + v["limit"] + "\nOFFSET " + v["offset"]
+	if pagination != expectedPagination {
+		t.Errorf("expected: %s for pagination, actual: %s", expectedPagination, pagination)
+	}
+
 	if queryValues == nil {
 		t.Errorf("expected: nil error, actual: %v", queryValues)
 	}
diff --git a/traffic_ops/traffic_ops_golang/deliveryservice/deliveryservices.go b/traffic_ops/traffic_ops_golang/deliveryservice/deliveryservices.go
index 313cabd..2a5af5b 100644
--- a/traffic_ops/traffic_ops_golang/deliveryservice/deliveryservices.go
+++ b/traffic_ops/traffic_ops_golang/deliveryservice/deliveryservices.go
@@ -819,7 +819,7 @@ func readGetDeliveryServices(params map[string]string, tx *sqlx.Tx, user *auth.C
 		"signingAlgorithm": dbhelpers.WhereColumnInfo{"ds.signing_algorithm", nil},
 	}
 
-	where, orderBy, queryValues, errs := dbhelpers.BuildWhereAndOrderBy(params, queryParamsToSQLCols)
+	where, orderBy, pagination, queryValues, errs := dbhelpers.BuildWhereAndOrderByAndPagination(params, queryParamsToSQLCols)
 	if len(errs) > 0 {
 		return nil, errs, tc.DataConflictError
 	}
@@ -833,7 +833,7 @@ func readGetDeliveryServices(params map[string]string, tx *sqlx.Tx, user *auth.C
 
 	where, queryValues = dbhelpers.AddTenancyCheck(where, queryValues, "ds.tenant_id", tenantIDs)
 
-	query := selectQuery() + where + orderBy
+	query := selectQuery() + where + orderBy + pagination
 
 	log.Debugln("generated deliveryServices query: " + query)
 	log.Debugf("executing with values: %++v\n", queryValues)
diff --git a/traffic_ops/traffic_ops_golang/deliveryservice/request/requests.go b/traffic_ops/traffic_ops_golang/deliveryservice/request/requests.go
index 6dc738b..bf6bde0 100644
--- a/traffic_ops/traffic_ops_golang/deliveryservice/request/requests.go
+++ b/traffic_ops/traffic_ops_golang/deliveryservice/request/requests.go
@@ -98,7 +98,7 @@ func (req *TODeliveryServiceRequest) Read() ([]interface{}, error, error, int) {
 		p["orderby"] = "xmlId"
 	}
 
-	where, orderBy, queryValues, errs := dbhelpers.BuildWhereAndOrderBy(p, queryParamsToQueryCols)
+	where, orderBy, pagination, queryValues, errs := dbhelpers.BuildWhereAndOrderByAndPagination(p, queryParamsToQueryCols)
 	if len(errs) > 0 {
 		return nil, util.JoinErrs(errs), nil, http.StatusBadRequest
 	}
@@ -108,7 +108,7 @@ func (req *TODeliveryServiceRequest) Read() ([]interface{}, error, error, int) {
 	}
 	where, queryValues = dbhelpers.AddTenancyCheck(where, queryValues, "CAST(r.deliveryservice->>'tenantId' AS bigint)", tenantIDs)
 
-	query := selectDeliveryServiceRequestsQuery() + where + orderBy
+	query := selectDeliveryServiceRequestsQuery() + where + orderBy + pagination
 	log.Debugln("Query is ", query)
 
 	rows, err := req.APIInfo().Tx.NamedQuery(query, queryValues)
diff --git a/traffic_ops/traffic_ops_golang/deliveryservice/servers/servers.go b/traffic_ops/traffic_ops_golang/deliveryservice/servers/servers.go
index 855a3a0..60a853c 100644
--- a/traffic_ops/traffic_ops_golang/deliveryservice/servers/servers.go
+++ b/traffic_ops/traffic_ops_golang/deliveryservice/servers/servers.go
@@ -509,7 +509,7 @@ func (dss *TODSSDeliveryService) Read() ([]interface{}, error, error, int) {
 		"xml_id": dbhelpers.WhereColumnInfo{"ds.xml_id", nil},
 		"xmlId":  dbhelpers.WhereColumnInfo{"ds.xml_id", nil},
 	}
-	where, orderBy, queryValues, errs := dbhelpers.BuildWhereAndOrderBy(params, queryParamsToSQLCols)
+	where, orderBy, pagination, queryValues, errs := dbhelpers.BuildWhereAndOrderByAndPagination(params, queryParamsToSQLCols)
 	if len(errs) > 0 {
 		return nil, nil, errors.New("reading server dses: " + util.JoinErrsStr(errs)), http.StatusInternalServerError
 	}
@@ -528,7 +528,7 @@ func (dss *TODSSDeliveryService) Read() ([]interface{}, error, error, int) {
 	}
 	where, queryValues = dbhelpers.AddTenancyCheck(where, queryValues, "ds.tenant_id", tenantIDs)
 
-	query := deliveryservice.GetDSSelectQuery() + where + orderBy
+	query := deliveryservice.GetDSSelectQuery() + where + orderBy + pagination
 	queryValues["server"] = dss.APIInfo().Params["id"]
 	log.Debugln("generated deliveryServices query: " + query)
 	log.Debugf("executing with values: %++v\n", queryValues)
diff --git a/traffic_ops/traffic_ops_golang/hwinfo/hwinfo.go b/traffic_ops/traffic_ops_golang/hwinfo/hwinfo.go
index ca84dce..263f581 100644
--- a/traffic_ops/traffic_ops_golang/hwinfo/hwinfo.go
+++ b/traffic_ops/traffic_ops_golang/hwinfo/hwinfo.go
@@ -52,11 +52,11 @@ func getHWInfo(tx *sqlx.Tx, params map[string]string) ([]tc.HWInfo, error) {
 		"val":            dbhelpers.WhereColumnInfo{"h.val", nil},
 		"lastUpdated":    dbhelpers.WhereColumnInfo{"h.last_updated", nil}, //TODO: this doesn't appear to work needs debugging
 	}
-	where, orderBy, queryValues, errs := dbhelpers.BuildWhereAndOrderBy(params, queryParamsToSQLCols)
+	where, orderBy, pagination, queryValues, errs := dbhelpers.BuildWhereAndOrderByAndPagination(params, queryParamsToSQLCols)
 	if len(errs) > 0 {
 		return nil, errors.New("getHWInfo building where clause: " + util.JoinErrsStr(errs))
 	}
-	query := selectHWInfoQuery() + where + orderBy
+	query := selectHWInfoQuery() + where + orderBy + pagination
 	log.Debugln("Query is ", query)
 
 	rows, err := tx.NamedQuery(query, queryValues)
diff --git a/traffic_ops/traffic_ops_golang/origin/origins.go b/traffic_ops/traffic_ops_golang/origin/origins.go
index 15cf341..4b2b507 100644
--- a/traffic_ops/traffic_ops_golang/origin/origins.go
+++ b/traffic_ops/traffic_ops_golang/origin/origins.go
@@ -162,7 +162,7 @@ func getOrigins(params map[string]string, tx *sqlx.Tx, user *auth.CurrentUser) (
 		"tenant":          dbhelpers.WhereColumnInfo{"o.tenant", api.IsInt},
 	}
 
-	where, orderBy, queryValues, errs := dbhelpers.BuildWhereAndOrderBy(params, queryParamsToSQLCols)
+	where, orderBy, pagination, queryValues, errs := dbhelpers.BuildWhereAndOrderByAndPagination(params, queryParamsToSQLCols)
 	if len(errs) > 0 {
 		return nil, errs, tc.DataConflictError
 	}
@@ -174,7 +174,7 @@ func getOrigins(params map[string]string, tx *sqlx.Tx, user *auth.CurrentUser) (
 	}
 	where, queryValues = dbhelpers.AddTenancyCheck(where, queryValues, "o.tenant", tenantIDs)
 
-	query := selectQuery() + where + orderBy
+	query := selectQuery() + where + orderBy + pagination
 	log.Debugln("Query is ", query)
 
 	rows, err = tx.NamedQuery(query, queryValues)
diff --git a/traffic_ops/traffic_ops_golang/parameter/parameters.go b/traffic_ops/traffic_ops_golang/parameter/parameters.go
index cf5ccaf..eeb4534 100644
--- a/traffic_ops/traffic_ops_golang/parameter/parameters.go
+++ b/traffic_ops/traffic_ops_golang/parameter/parameters.go
@@ -121,12 +121,12 @@ func (pa *TOParameter) Create() (error, error, int) {
 
 func (param *TOParameter) Read() ([]interface{}, error, error, int) {
 	queryParamsToQueryCols := param.ParamColumns()
-	where, orderBy, queryValues, errs := dbhelpers.BuildWhereAndOrderBy(param.APIInfo().Params, queryParamsToQueryCols)
+	where, orderBy, pagination, queryValues, errs := dbhelpers.BuildWhereAndOrderByAndPagination(param.APIInfo().Params, queryParamsToQueryCols)
 	if len(errs) > 0 {
 		return nil, util.JoinErrs(errs), nil, http.StatusBadRequest
 	}
 
-	query := selectQuery() + where + ParametersGroupBy() + orderBy
+	query := selectQuery() + where + ParametersGroupBy() + orderBy + pagination
 	rows, err := param.ReqInfo.Tx.NamedQuery(query, queryValues)
 	if err != nil {
 		return nil, nil, errors.New("querying " + param.GetType() + ": " + err.Error()), http.StatusInternalServerError
diff --git a/traffic_ops/traffic_ops_golang/profile/profiles.go b/traffic_ops/traffic_ops_golang/profile/profiles.go
index eb7d87e..6f09542 100644
--- a/traffic_ops/traffic_ops_golang/profile/profiles.go
+++ b/traffic_ops/traffic_ops_golang/profile/profiles.go
@@ -109,7 +109,7 @@ func (prof *TOProfile) Read() ([]interface{}, error, error, int) {
 		NameQueryParam: dbhelpers.WhereColumnInfo{"prof.name", nil},
 		IDQueryParam:   dbhelpers.WhereColumnInfo{"prof.id", api.IsInt},
 	}
-	where, orderBy, queryValues, errs := dbhelpers.BuildWhereAndOrderBy(prof.APIInfo().Params, queryParamsToQueryCols)
+	where, orderBy, pagination, queryValues, errs := dbhelpers.BuildWhereAndOrderByAndPagination(prof.APIInfo().Params, queryParamsToQueryCols)
 
 	// Narrow down if the query parameter is 'param'
 
@@ -125,7 +125,7 @@ func (prof *TOProfile) Read() ([]interface{}, error, error, int) {
 		return nil, util.JoinErrs(errs), nil, http.StatusBadRequest
 	}
 
-	query := selectProfilesQuery() + where + orderBy
+	query := selectProfilesQuery() + where + orderBy + pagination
 	log.Debugln("Query is ", query)
 
 	rows, err := prof.ReqInfo.Tx.NamedQuery(query, queryValues)
diff --git a/traffic_ops/traffic_ops_golang/server/servers.go b/traffic_ops/traffic_ops_golang/server/servers.go
index 7c564ca..c7411fe 100644
--- a/traffic_ops/traffic_ops_golang/server/servers.go
+++ b/traffic_ops/traffic_ops_golang/server/servers.go
@@ -233,12 +233,12 @@ FULL OUTER JOIN deliveryservice_server dss ON dss.server = s.id
 		log.Debugf("Servers for ds %d; uses mids? %v\n", dsID, usesMids)
 	}
 
-	where, orderBy, queryValues, errs := dbhelpers.BuildWhereAndOrderBy(params, queryParamsToSQLCols)
+	where, orderBy, pagination, queryValues, errs := dbhelpers.BuildWhereAndOrderByAndPagination(params, queryParamsToSQLCols)
 	if len(errs) > 0 {
 		return nil, errs, tc.DataConflictError
 	}
 
-	query := selectQuery() + queryAddition + where + orderBy
+	query := selectQuery() + queryAddition + where + orderBy + pagination
 	log.Debugln("Query is ", query)
 
 	rows, err := tx.NamedQuery(query, queryValues)
diff --git a/traffic_ops/traffic_ops_golang/steeringtargets/steeringtargets.go b/traffic_ops/traffic_ops_golang/steeringtargets/steeringtargets.go
index d876c4b..a9ccfb0 100644
--- a/traffic_ops/traffic_ops_golang/steeringtargets/steeringtargets.go
+++ b/traffic_ops/traffic_ops_golang/steeringtargets/steeringtargets.go
@@ -111,11 +111,11 @@ func read(tx *sqlx.Tx, parameters map[string]string, user *auth.CurrentUser) ([]
 		"deliveryservice": dbhelpers.WhereColumnInfo{"st.deliveryservice", api.IsInt},
 		"target":          dbhelpers.WhereColumnInfo{"st.target", api.IsInt},
 	}
-	where, orderBy, queryValues, errs := dbhelpers.BuildWhereAndOrderBy(parameters, queryParamsToQueryCols)
+	where, orderBy, pagination, queryValues, errs := dbhelpers.BuildWhereAndOrderByAndPagination(parameters, queryParamsToQueryCols)
 	if len(errs) > 0 {
 		return nil, nil, util.JoinErrs(errs), http.StatusBadRequest
 	}
-	query := selectQuery() + where + orderBy
+	query := selectQuery() + where + orderBy + pagination
 
 	userTenants, err := tenant.GetUserTenantListTx(*user, tx.Tx)
 	if err != nil {
diff --git a/traffic_ops/traffic_ops_golang/user/user.go b/traffic_ops/traffic_ops_golang/user/user.go
index 44c5845..fc5484d 100644
--- a/traffic_ops/traffic_ops_golang/user/user.go
+++ b/traffic_ops/traffic_ops_golang/user/user.go
@@ -185,7 +185,7 @@ func checkTenancy(tenantID *int, tenantIDs []int) bool {
 func (this *TOUser) Read() ([]interface{}, error, error, int) {
 
 	inf := this.APIInfo()
-	where, orderBy, queryValues, errs := dbhelpers.BuildWhereAndOrderBy(inf.Params, this.ParamColumns())
+	where, orderBy, pagination, queryValues, errs := dbhelpers.BuildWhereAndOrderByAndPagination(inf.Params, this.ParamColumns())
 	if len(errs) > 0 {
 		return nil, util.JoinErrs(errs), nil, http.StatusBadRequest
 	}
@@ -196,7 +196,7 @@ func (this *TOUser) Read() ([]interface{}, error, error, int) {
 	}
 	where, queryValues = dbhelpers.AddTenancyCheck(where, queryValues, "u.tenant_id", tenantIDs)
 
-	query := this.SelectQuery() + where + orderBy
+	query := this.SelectQuery() + where + orderBy + pagination
 	rows, err := inf.Tx.NamedQuery(query, queryValues)
 	if err != nil {
 		return nil, nil, fmt.Errorf("querying users : %v", err), http.StatusInternalServerError