惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
G
Google Developers Blog
Hugging Face - Blog
Hugging Face - Blog
博客园 - 【当耐特】
S
SegmentFault 最新的问题
宝玉的分享
宝玉的分享
博客园 - Franky
博客园_首页
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
WordPress大学
WordPress大学
有赞技术团队
有赞技术团队
月光博客
月光博客
博客园 - 聂微东
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
小众软件
小众软件
Microsoft Security Blog
Microsoft Security Blog
Last Week in AI
Last Week in AI
Vercel News
Vercel News
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
爱范儿
爱范儿
J
Java Code Geeks
博客园 - 叶小钗
Engineering at Meta
Engineering at Meta
阮一峰的网络日志
阮一峰的网络日志

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant
Why your Supabase query stops at exactly 1000 rows (and n...
Michel Faure · 2026-05-19 · via DEV Community

The night a dropdown lied to me

Eleven at night, late April. A user of my internal tool reports an incomplete filter on a dropdown. My first diagnosis blames a sub-filter on the UI side. I take it apart, I isolate the dataset source, I re-run without filters. The counter returns a thousand on the nose. The table holds one thousand two hundred and three rows. I walk up the pipeline, level by level, all the way to the source query. Four levels higher, the culprit appears: a .select() chained on .from(), no .order(), no .limit(). The query succeeds. It lies.

The mechanism

Any PostgREST query that doesn't declare its ORDER BY receives an internal ctid sort — the physical tuple identifier in Postgres — plus a 1000-row Range HTTP cap applied by Supabase. The query succeeds. No exception, no warning, no Sentry breadcrumb. The client gets a subset whose order depends on the table's UPDATE and DELETE history, reshuffled after a VACUUM FULL or pg_repack. The bug only exists above one thousand rows in production.

// silently capped at 1000 rows
await supabase.from('events').select('*').eq('type', 'login')

// the Range HTTP cap applies on a stable sort
await supabase.from('events').select('*').eq('type', 'login').order('id')

Enter fullscreen mode Exit fullscreen mode

The ESLint rule that closes the door

The pattern is too quiet to live in code review alone. I moved it to lint, as an AST visitor on CallExpression, that requires a .select() chained on .from() to carry an .order() somewhere downstream, unless the chain terminates with .single(), .maybeSingle(), or an explicit .limit() below or equal to a thousand. It's one of five structural guards a workable Supabase rule needs. Without the others, the noise drowns the rule in under an hour.

export default {
  meta: { type: 'problem', messages: { unordered:
    'select() without .order() falls back to ORDER BY ctid.' } },
  create(context) {
    return {
      CallExpression(node) {
        if (node.callee?.property?.name !== 'select') return
        if (!chainContainsFromCall(node.callee.object)) return
        if (chainHasSafeTerminator(node)) return        // .single, .order, .csv...
        if (selectOptsHasHeadTrue(node)) return         // count head
        if (chainEndsAtAssignment(node)) return         // let q = supabase...
        if (chainIsInsideHelper(node, 'fetchAll')) return
        context.report({ node, messageId: 'unordered' })
      },
    }
  },
}

Enter fullscreen mode Exit fullscreen mode

The real scale

Once the rule was promoted to error, the first audit raised one hundred and seventy-eight alerts, spread over fifty-six files. Forty percent were false positives: variable reassigned at a distance, write returning, pagination helper injecting its own .order(), count head, single-row terminator. The five structural guards brought the noise down to one hundred and eight real targets before touching a single line of application code.

The rule

Every non-trivial .from(X).select(...) chain carries an explicit .order(). No option, no lukewarm.


Full rule, before/after pair and fetchAll helper, pseudonymized:
github.com/michelfaure/rembrandt-samples/tree/main/postgrest-row-cap

This silent PostgREST default is exactly the archetypal case of R12 of the Counterpart Toolkit ("cite the official text, materialise vendor defaults"). 14 rules, install in 1 command: github.com/michelfaure/doctrine-counterpart