You can’t return structs but you can return the fields in the struct, one at a time. Same for arrays. You could return a fixed size array up to 16 elements (8 for string) but in the case of an unbounded array length, that’s useless.
The design suggests iterating over rows one at a time. This works within the limits we have to deal with and it won’t disappoint at scale.
In case it helps, I summarized some simpler patterns and I explain their limitations over here: https://ethereum.stackexchange.com/questions/13167/are-there-well-solved-and-simple-storage-patterns-for-solidity. It gives you a sort of mental map to use when considering the “forever” requirements of a data set. You can pick the pattern that matches and proceed with confidence.