mirror of
https://github.com/stashapp/stash.git
synced 2025-12-06 16:34:02 +01:00
Handle large and all entity queries (#3544)
* Remove upper page size limit * Batch GetMany function * Remove upper query limit from UI
This commit is contained in:
parent
ac67d640db
commit
58852f86fe
10 changed files with 130 additions and 75 deletions
|
|
@ -96,15 +96,15 @@ func (ff FindFilterType) GetPage() int {
|
||||||
func (ff FindFilterType) GetPageSize() int {
|
func (ff FindFilterType) GetPageSize() int {
|
||||||
const defaultPerPage = 25
|
const defaultPerPage = 25
|
||||||
const minPerPage = 0
|
const minPerPage = 0
|
||||||
const maxPerPage = 1000
|
|
||||||
|
|
||||||
if ff.PerPage == nil {
|
if ff.PerPage == nil {
|
||||||
return defaultPerPage
|
return defaultPerPage
|
||||||
}
|
}
|
||||||
|
|
||||||
if *ff.PerPage > maxPerPage {
|
// removed the maxPerPage check. We already all -1 to indicate all results
|
||||||
return maxPerPage
|
// so there is no conceivable reason we should limit the page size
|
||||||
} else if *ff.PerPage < minPerPage {
|
|
||||||
|
if *ff.PerPage < minPerPage {
|
||||||
// negative page sizes should return all results
|
// negative page sizes should return all results
|
||||||
// this is a sanity check in case GetPageSize is
|
// this is a sanity check in case GetPageSize is
|
||||||
// called with a negative page size.
|
// called with a negative page size.
|
||||||
|
|
|
||||||
20
pkg/sqlite/batch.go
Normal file
20
pkg/sqlite/batch.go
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
package sqlite
|
||||||
|
|
||||||
|
const defaultBatchSize = 1000
|
||||||
|
|
||||||
|
// batchExec executes the provided function in batches of the provided size.
|
||||||
|
func batchExec(ids []int, batchSize int, fn func(batch []int) error) error {
|
||||||
|
for i := 0; i < len(ids); i += batchSize {
|
||||||
|
end := i + batchSize
|
||||||
|
if end > len(ids) {
|
||||||
|
end = len(ids)
|
||||||
|
}
|
||||||
|
|
||||||
|
batch := ids[i:end]
|
||||||
|
if err := fn(batch); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
@ -363,17 +363,23 @@ func (qb *GalleryStore) Find(ctx context.Context, id int) (*models.Gallery, erro
|
||||||
}
|
}
|
||||||
|
|
||||||
func (qb *GalleryStore) FindMany(ctx context.Context, ids []int) ([]*models.Gallery, error) {
|
func (qb *GalleryStore) FindMany(ctx context.Context, ids []int) ([]*models.Gallery, error) {
|
||||||
q := qb.selectDataset().Prepared(true).Where(qb.table().Col(idColumn).In(ids))
|
|
||||||
unsorted, err := qb.getMany(ctx, q)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
galleries := make([]*models.Gallery, len(ids))
|
galleries := make([]*models.Gallery, len(ids))
|
||||||
|
|
||||||
for _, s := range unsorted {
|
if err := batchExec(ids, defaultBatchSize, func(batch []int) error {
|
||||||
i := intslice.IntIndex(ids, s.ID)
|
q := qb.selectDataset().Prepared(true).Where(qb.table().Col(idColumn).In(batch))
|
||||||
galleries[i] = s
|
unsorted, err := qb.getMany(ctx, q)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, s := range unsorted {
|
||||||
|
i := intslice.IntIndex(ids, s.ID)
|
||||||
|
galleries[i] = s
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}); err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
for i := range galleries {
|
for i := range galleries {
|
||||||
|
|
|
||||||
|
|
@ -260,17 +260,23 @@ func (qb *ImageStore) Find(ctx context.Context, id int) (*models.Image, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (qb *ImageStore) FindMany(ctx context.Context, ids []int) ([]*models.Image, error) {
|
func (qb *ImageStore) FindMany(ctx context.Context, ids []int) ([]*models.Image, error) {
|
||||||
q := qb.selectDataset().Prepared(true).Where(qb.table().Col(idColumn).In(ids))
|
|
||||||
unsorted, err := qb.getMany(ctx, q)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
images := make([]*models.Image, len(ids))
|
images := make([]*models.Image, len(ids))
|
||||||
|
|
||||||
for _, s := range unsorted {
|
if err := batchExec(ids, defaultBatchSize, func(batch []int) error {
|
||||||
i := intslice.IntIndex(ids, s.ID)
|
q := qb.selectDataset().Prepared(true).Where(qb.table().Col(idColumn).In(batch))
|
||||||
images[i] = s
|
unsorted, err := qb.getMany(ctx, q)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, s := range unsorted {
|
||||||
|
i := intslice.IntIndex(ids, s.ID)
|
||||||
|
images[i] = s
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}); err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
for i := range images {
|
for i := range images {
|
||||||
|
|
|
||||||
|
|
@ -70,17 +70,23 @@ func (qb *movieQueryBuilder) Find(ctx context.Context, id int) (*models.Movie, e
|
||||||
|
|
||||||
func (qb *movieQueryBuilder) FindMany(ctx context.Context, ids []int) ([]*models.Movie, error) {
|
func (qb *movieQueryBuilder) FindMany(ctx context.Context, ids []int) ([]*models.Movie, error) {
|
||||||
tableMgr := movieTableMgr
|
tableMgr := movieTableMgr
|
||||||
q := goqu.Select("*").From(tableMgr.table).Where(tableMgr.byIDInts(ids...))
|
|
||||||
unsorted, err := qb.getMany(ctx, q)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
ret := make([]*models.Movie, len(ids))
|
ret := make([]*models.Movie, len(ids))
|
||||||
|
|
||||||
for _, s := range unsorted {
|
if err := batchExec(ids, defaultBatchSize, func(batch []int) error {
|
||||||
i := intslice.IntIndex(ids, s.ID)
|
q := goqu.Select("*").From(tableMgr.table).Where(tableMgr.byIDInts(batch...))
|
||||||
ret[i] = s
|
unsorted, err := qb.getMany(ctx, q)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, s := range unsorted {
|
||||||
|
i := intslice.IntIndex(ids, s.ID)
|
||||||
|
ret[i] = s
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}); err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
for i := range ret {
|
for i := range ret {
|
||||||
|
|
|
||||||
|
|
@ -299,17 +299,23 @@ func (qb *PerformerStore) Find(ctx context.Context, id int) (*models.Performer,
|
||||||
|
|
||||||
func (qb *PerformerStore) FindMany(ctx context.Context, ids []int) ([]*models.Performer, error) {
|
func (qb *PerformerStore) FindMany(ctx context.Context, ids []int) ([]*models.Performer, error) {
|
||||||
tableMgr := performerTableMgr
|
tableMgr := performerTableMgr
|
||||||
q := goqu.Select("*").From(tableMgr.table).Where(tableMgr.byIDInts(ids...))
|
|
||||||
unsorted, err := qb.getMany(ctx, q)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
ret := make([]*models.Performer, len(ids))
|
ret := make([]*models.Performer, len(ids))
|
||||||
|
|
||||||
for _, s := range unsorted {
|
if err := batchExec(ids, defaultBatchSize, func(batch []int) error {
|
||||||
i := intslice.IntIndex(ids, s.ID)
|
q := goqu.Select("*").From(tableMgr.table).Where(tableMgr.byIDInts(batch...))
|
||||||
ret[i] = s
|
unsorted, err := qb.getMany(ctx, q)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, s := range unsorted {
|
||||||
|
i := intslice.IntIndex(ids, s.ID)
|
||||||
|
ret[i] = s
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}); err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
for i := range ret {
|
for i := range ret {
|
||||||
|
|
|
||||||
|
|
@ -364,18 +364,24 @@ func (qb *SceneStore) Find(ctx context.Context, id int) (*models.Scene, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (qb *SceneStore) FindMany(ctx context.Context, ids []int) ([]*models.Scene, error) {
|
func (qb *SceneStore) FindMany(ctx context.Context, ids []int) ([]*models.Scene, error) {
|
||||||
table := qb.table()
|
|
||||||
q := qb.selectDataset().Prepared(true).Where(table.Col(idColumn).In(ids))
|
|
||||||
unsorted, err := qb.getMany(ctx, q)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
scenes := make([]*models.Scene, len(ids))
|
scenes := make([]*models.Scene, len(ids))
|
||||||
|
|
||||||
for _, s := range unsorted {
|
table := qb.table()
|
||||||
i := intslice.IntIndex(ids, s.ID)
|
if err := batchExec(ids, defaultBatchSize, func(batch []int) error {
|
||||||
scenes[i] = s
|
q := qb.selectDataset().Prepared(true).Where(table.Col(idColumn).In(batch))
|
||||||
|
unsorted, err := qb.getMany(ctx, q)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, s := range unsorted {
|
||||||
|
i := intslice.IntIndex(ids, s.ID)
|
||||||
|
scenes[i] = s
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}); err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
for i := range scenes {
|
for i := range scenes {
|
||||||
|
|
|
||||||
|
|
@ -80,17 +80,23 @@ func (qb *studioQueryBuilder) Find(ctx context.Context, id int) (*models.Studio,
|
||||||
|
|
||||||
func (qb *studioQueryBuilder) FindMany(ctx context.Context, ids []int) ([]*models.Studio, error) {
|
func (qb *studioQueryBuilder) FindMany(ctx context.Context, ids []int) ([]*models.Studio, error) {
|
||||||
tableMgr := studioTableMgr
|
tableMgr := studioTableMgr
|
||||||
q := goqu.Select("*").From(tableMgr.table).Where(tableMgr.byIDInts(ids...))
|
|
||||||
unsorted, err := qb.getMany(ctx, q)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
ret := make([]*models.Studio, len(ids))
|
ret := make([]*models.Studio, len(ids))
|
||||||
|
|
||||||
for _, s := range unsorted {
|
if err := batchExec(ids, defaultBatchSize, func(batch []int) error {
|
||||||
i := intslice.IntIndex(ids, s.ID)
|
q := goqu.Select("*").From(tableMgr.table).Where(tableMgr.byIDInts(batch...))
|
||||||
ret[i] = s
|
unsorted, err := qb.getMany(ctx, q)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, s := range unsorted {
|
||||||
|
i := intslice.IntIndex(ids, s.ID)
|
||||||
|
ret[i] = s
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}); err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
for i := range ret {
|
for i := range ret {
|
||||||
|
|
|
||||||
|
|
@ -98,17 +98,23 @@ func (qb *tagQueryBuilder) Find(ctx context.Context, id int) (*models.Tag, error
|
||||||
|
|
||||||
func (qb *tagQueryBuilder) FindMany(ctx context.Context, ids []int) ([]*models.Tag, error) {
|
func (qb *tagQueryBuilder) FindMany(ctx context.Context, ids []int) ([]*models.Tag, error) {
|
||||||
tableMgr := tagTableMgr
|
tableMgr := tagTableMgr
|
||||||
q := goqu.Select("*").From(tableMgr.table).Where(tableMgr.byIDInts(ids...))
|
|
||||||
unsorted, err := qb.getMany(ctx, q)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
ret := make([]*models.Tag, len(ids))
|
ret := make([]*models.Tag, len(ids))
|
||||||
|
|
||||||
for _, s := range unsorted {
|
if err := batchExec(ids, defaultBatchSize, func(batch []int) error {
|
||||||
i := intslice.IntIndex(ids, s.ID)
|
q := goqu.Select("*").From(tableMgr.table).Where(tableMgr.byIDInts(batch...))
|
||||||
ret[i] = s
|
unsorted, err := qb.getMany(ctx, q)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, s := range unsorted {
|
||||||
|
i := intslice.IntIndex(ids, s.ID)
|
||||||
|
ret[i] = s
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}); err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
for i := range ret {
|
for i := range ret {
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,6 @@ import {
|
||||||
} from "@fortawesome/free-solid-svg-icons";
|
} from "@fortawesome/free-solid-svg-icons";
|
||||||
import { useDebounce } from "src/hooks/debounce";
|
import { useDebounce } from "src/hooks/debounce";
|
||||||
|
|
||||||
const maxPageSize = 1000;
|
|
||||||
interface IListFilterProps {
|
interface IListFilterProps {
|
||||||
onFilterUpdate: (newFilter: ListFilterModel) => void;
|
onFilterUpdate: (newFilter: ListFilterModel) => void;
|
||||||
filter: ListFilterModel;
|
filter: ListFilterModel;
|
||||||
|
|
@ -134,11 +133,6 @@ export const ListFilter: React.FC<IListFilterProps> = ({
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// don't allow page sizes over 1000
|
|
||||||
if (pp > maxPageSize) {
|
|
||||||
pp = maxPageSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
const newFilter = cloneDeep(filter);
|
const newFilter = cloneDeep(filter);
|
||||||
newFilter.itemsPerPage = pp;
|
newFilter.itemsPerPage = pp;
|
||||||
newFilter.currentPage = 1;
|
newFilter.currentPage = 1;
|
||||||
|
|
@ -377,7 +371,6 @@ export const ListFilter: React.FC<IListFilterProps> = ({
|
||||||
<Form.Control
|
<Form.Control
|
||||||
type="number"
|
type="number"
|
||||||
min={1}
|
min={1}
|
||||||
max={maxPageSize}
|
|
||||||
className="text-input"
|
className="text-input"
|
||||||
ref={perPageInput}
|
ref={perPageInput}
|
||||||
onKeyPress={(e: React.KeyboardEvent<HTMLInputElement>) => {
|
onKeyPress={(e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue