DynamoSQL uses stateless offset pagination. There is no server-side cursor — each page is a fresh query execution that skips past previously-returned rows.
How It Works
options.maxRows — maximum rows to return per request. Defaults to 100.
options.resumeIdx — the row offset to start from. Omit (or pass 0) for the first page.
When the response contains exactly maxRows rows, the engine sets resumeIdx in the response to firstRowIdx + maxRows. Pass that value as options.resumeIdx on the next request to get the next page.
When resumeIdx is absent from the response, the result set is exhausted.
Check for resumeIdx with "resumeIdx" in data (presence check), not data.resumeIdx !== null or data.resumeIdx !== undefined. The field is omitted entirely when there are no more rows — it is never set to null.
Statefulness Caveat
This is offset pagination — the engine re-executes the full query and skips rows on each page. If DynamoDB data changes between page requests, rows may be duplicated or skipped at page boundaries. This approach is appropriate for analytics and reporting workloads, but is not suitable for transactional reads where consistency across pages is required.
import requests
def paginate(token, sql, max_rows=100):
resume_idx = 0
while True:
resp = requests.post(
"https://api.dynamosql.com/v1/query",
headers={"Authorization": f"Bearer {token}"},
json={
"sql": sql,
"mode": "execute",
"options": {"maxRows": max_rows, "resumeIdx": resume_idx},
},
)
body = resp.json()
if not body["success"]:
raise RuntimeError(body["error"])
data = body["data"]
yield from data["data"]
if "resumeIdx" not in data:
break
resume_idx = data["resumeIdx"]
# Usage
for row in paginate(token, "SELECT * FROM myschema.orders WHERE status = 'shipped'"):
print(row)