WEB / WEB-18.S01
Query/data separation in SQL and NoSQL
Follow typed intents, application-owned queries, and projected results in a public catalog.
1. Core · Mechanism
Read every scenario without motion
Bound SQL title
- Catalog intent ready
The caller’s catalog intent carries backend=sql; title=O'Reilly; sort=id. The query builder will validate these fields; no statement/filter or completed row result exists yet.
- Input facts
- backend=sql; title=O'Reilly; sort=id
- Query builder
- No query contract
- Data owner
- No lookup; count=0
- Result view
- No result
- Field-type decision
The closed backend/title/sort keys, backend enum and scalar title pass. Title is within 1–40 code points; a string sort still needs the allowed-value decision at the next stage.
- Input facts
- backend=sql; title=O'Reilly; sort=id
- Query builder
- Fields typed
- Data owner
- No lookup; count=0
- Result view
- No result
- Query-contract selection
sort=id selects the application’s fixed by-id SELECT. O'Reilly/public form its bound title/visibility tuple; these values are not concatenated into statement syntax.
- Input facts
- backend=sql; title=O'Reilly; sort=id
- Query builder
- by-id + bound values
- Data owner
- No lookup; count=0
- Result view
- No result
- Local-lookup decision
The local SQLite SELECT completes: title=O'Reilly and visibility=public select id=2 with queries=1. Seeds remain unchanged and the result view has not consumed the projection yet.
- Input facts
- backend=sql; title=O'Reilly; sort=id
- Query builder
- by-id + bound values
- Data owner
- Lookup complete; count=1; rows=1
- Result view
- No result
- Projection ready
Completed rows are projected to id/title only; rejected branches retain null rows while the consumer still has no result.
- Input facts
- backend=sql; title=O'Reilly; sort=id
- Query builder
- by-id + bound values
- Data owner
- Projected rows=1; count=1
- Result view
- No result
- Result-view decision
The view consumes only a completed projected result; rejection is not shown as success or an empty match and the seed is unchanged.
- Input facts
- backend=sql; title=O'Reilly; sort=id
- Query builder
- by-id + bound values
- Data owner
- Projected rows=1; count=1
- Result view
- Public result: id=2, title=O'Reilly
Scalar filter
- Catalog intent ready
The caller’s catalog intent carries backend=document; title=Tea; sort=id. The query builder will validate these fields; no statement/filter or completed row result exists yet.
- Input facts
- backend=document; title=Tea; sort=id
- Query builder
- No query contract
- Data owner
- No lookup; count=0
- Result view
- No result
- Field-type decision
The closed backend/title/sort keys, backend enum and scalar title pass. Title is within 1–40 code points; a string sort still needs the allowed-value decision at the next stage.
- Input facts
- backend=document; title=Tea; sort=id
- Query builder
- Fields typed
- Data owner
- No lookup; count=0
- Result view
- No result
- Query-contract selection
The application constructs {title:Tea,visibility:public} equality filtering from the admitted Tea string and selects sort=id. It does not copy a caller object as the filter; this narrow matcher is not MongoDB.
- Input facts
- backend=document; title=Tea; sort=id
- Query builder
- Scalar public filter
- Data owner
- No lookup; count=0
- Result view
- No result
- Local-lookup decision
The local literal matcher finds id=1 and id=3 for title=Tea and visibility=public, ordering them by id. queries=1 with two matches; the view has not consumed this result yet.
- Input facts
- backend=document; title=Tea; sort=id
- Query builder
- Scalar public filter
- Data owner
- Lookup complete; count=1; rows=2
- Result view
- No result
- Projection ready
Completed rows are projected to id/title only; rejected branches retain null rows while the consumer still has no result.
- Input facts
- backend=document; title=Tea; sort=id
- Query builder
- Scalar public filter
- Data owner
- Projected rows=2; count=1
- Result view
- No result
- Result-view decision
The view consumes only a completed projected result; rejection is not shown as success or an empty match and the seed is unchanged.
- Input facts
- backend=document; title=Tea; sort=id
- Query builder
- Scalar public filter
- Data owner
- Projected rows=2; count=1
- Result view
- Public result: Tea, id=1 and id=3
Object title
- Catalog intent ready
The caller’s catalog intent carries backend=document; title={display:Tea}; sort=id. The query builder will validate these fields; no statement/filter or completed row result exists yet.
- Input facts
- backend=document; title={display:Tea}; sort=id
- Query builder
- No query contract
- Data owner
- No lookup; count=0
- Result view
- No result
- Field-type decision
title={display:Tea} can be a valid plain object but fails title:string. The reason is field-type; sort checking, query construction and data-owner lookup do not begin.
- Input facts
- backend=document; title={display:Tea}; sort=id
- Query builder
- Rejected: field-type
- Data owner
- No lookup; count=0
- Result view
- No result
- Query-contract selection
After type admission the sort enum is chosen; unknown sort emits no statement and failed types skip sort evaluation.
- Input facts
- backend=document; title={display:Tea}; sort=id
- Query builder
- Sort not evaluated
- Data owner
- No lookup; count=0
- Result view
- No result
- Local-lookup decision
This denied branch invokes neither the SQLite driver nor the literal document matcher. queries=0 is not the empty result of an executed query; no earlier result is reused for this intent.
- Input facts
- backend=document; title={display:Tea}; sort=id
- Query builder
- Sort not evaluated
- Data owner
- No lookup; count=0
- Result view
- No result
- No projection produced
Without a lookup there is no projection, so rows remains null. This differs from rows=[] after an admitted Bread search with no match; seed data stays unchanged.
- Input facts
- backend=document; title={display:Tea}; sort=id
- Query builder
- Sort not evaluated
- Data owner
- Rows null; count=0
- Result view
- No result
- Result-view decision
The view consumes only a completed projected result; rejection is not shown as success or an empty match and the seed is unchanged.
- Input facts
- backend=document; title={display:Tea}; sort=id
- Query builder
- Sort not evaluated
- Data owner
- Rows null; count=0
- Result view
- No result; field-type
Unknown sort
- Catalog intent ready
The caller’s catalog intent carries backend=sql; title=Tea; sort=rating. The query builder will validate these fields; no statement/filter or completed row result exists yet.
- Input facts
- backend=sql; title=Tea; sort=rating
- Query builder
- No query contract
- Data owner
- No lookup; count=0
- Result view
- No result
- Field-type decision
Closed keys and scalar title/string types are checked; an ordinary display object does not become an admitted title.
- Input facts
- backend=sql; title=Tea; sort=rating
- Query builder
- Fields typed
- Data owner
- No lookup; count=0
- Result view
- No result
- Query-contract selection
Backend and scalar-title types are valid, but sort=rating is outside the allowed id/title enum. The reason is unknown-sort; statementId=null, filter=null and queries=0 remain.
- Input facts
- backend=sql; title=Tea; sort=rating
- Query builder
- Rejected: unknown-sort
- Data owner
- No lookup; count=0
- Result view
- No result
- Local-lookup decision
This denied branch invokes neither the SQLite driver nor the literal document matcher. queries=0 is not the empty result of an executed query; no earlier result is reused for this intent.
- Input facts
- backend=sql; title=Tea; sort=rating
- Query builder
- Rejected: unknown-sort
- Data owner
- No lookup; count=0
- Result view
- No result
- No projection produced
Without a lookup there is no projection, so rows remains null. This differs from rows=[] after an admitted Bread search with no match; seed data stays unchanged.
- Input facts
- backend=sql; title=Tea; sort=rating
- Query builder
- Rejected: unknown-sort
- Data owner
- Rows null; count=0
- Result view
- No result
- Result-view decision
The view consumes only a completed projected result; rejection is not shown as success or an empty match and the seed is unchanged.
- Input facts
- backend=sql; title=Tea; sort=rating
- Query builder
- Rejected: unknown-sort
- Data owner
- Rows null; count=0
- Result view
- No result; unknown-sort
ACTORS · STATE · DATA FLOW
Query structure and search values have different owners
Atlas public catalog offers exact title matching and two approved sort choices. The caller supplies a book title; the application chooses the table, fields, comparison, and projection. O'Reilly is an ordinary title. Its apostrophe does not make it invalid business input. In this example the SQL statement stays fixed while title and public visibility are separate bound values passed to the driver. The book name is never assembled as a piece of statement syntax.
The query/data distinction starts before interpretation. The handler produces a typed intent containing only backend, title, and sort. Backend is a sql/document enum for this teaching profile; a real product need not let clients choose database backends. Title is a string of 1–40 code points and sort is id or title. That domain contract does not replace parameterization. Validation decides which operation is admissible; binding preserves the role of an admitted value inside the query.
The SQL driver receives two distinct arguments
The artifact contains two prewritten SELECT statements, by-id and by-title. Both specify the id/title projection and equality conditions for title and visibility. Python sqlite3 execute receives the statement and the (title, public) tuple separately. The native check opens its own :memory: database, inserts three ordinary catalog rows, and reads only id=2 for O'Reilly. Input admission is not query execution; the view cannot show a result row before execute completes.
With Python qmark binding, the sequence length must match the placeholder count. Named placeholders expect a mapping. These are driver contracts, separate from field validation and resource authorization. The value slots of the two fixed statements do not change. The animation does not guess every internal prepare/cache operation or another SQL engine’s dialect. The evidence belongs to the selected SQLite invocation and its observed row list.
Sorting is not a value slot
A title value and an ORDER BY column choice are different responsibilities. Putting title in a bound value slot does not turn it into a column identifier. The application maps id and title to its own two fixed statements. An unknown sort such as rating is rejected before executing a query. This profile admits no arbitrary identifier, raw SQL fragment, or direction string. Choosing a sort option does not delegate authorship of the query language.
A form’s appearance cannot prove this distinction. A title from a text box may be bound as data, while a sort from a dropdown still needs a server-side enum check. Two UI options are not evidence of the server contract. In the unknown-sort branch the selected statement stays null, the data owner records query count=0, and the result view has no result. Missing intent is a separate NOT-COMPUTED state; unknown and missing are different decisions.
A document filter also belongs to a query language
The NoSQL label does not remove an interpretation boundary. The application validates a scalar title before constructing its own equality filter. Passing a client object through as the filter confuses application-owned query structure with client-owned data structure. Here the ordinary {display: Tea} object does not satisfy title:string and is rejected before any document lookup. Being a parsed object does not make it an admitted title value.
The document branch is a narrow Python literal matcher: application-built title and visibility fields select exact matches from its own seed documents, sort them by id or title, and project id/title. This matcher is not a MongoDB server, BSON codec, cursor, or ODM. MongoDB find returns a cursor; the example’s ready list does not reproduce all cursor behavior. The animation’s local Data owner responsibility keeps that limit explicit.
A correct query still needs the right access decision
Binding preserves query structure; it does not decide which row may be disclosed to which caller. This catalog uses fixed teaching data selected for public visibility. Private resources, tenant membership, service account privileges, and field-level disclosure require separate policy in a real application. The public scope condition is application-owned; no new scope decision is inferred from the title field.
An accepted query returning an empty row list is also different from rejection. Type/schema failure, unknown sort, driver failure, and an accepted lookup with no match need distinct outcomes. The result view changes only after lookup and projection complete. This distinction informs retry and error handlers: reporting a lookup that never ran as an empty result hides what the application actually checked. This artifact performs only SELECT/literal lookup, with no mutation, network, or production authorization.
2. Red · Trust & Impact
Read every scenario without motion
Field type
- The query-intent profile was read
The document backend expects title as a scalar string. The wrong-type input has title={display:Tea}; this ordinary object may be valid JSON without being an accepted title type.
- Caller intent
- Backend: document
- Typed query factory
- Intent validation pending
- Data-owner boundary
- Query not executed
- Value and structure responsibilities were separated
A parsed object is not copied directly into a query filter. The application builds a new equality filter from a typed title; an unaccepted type never reaches that factory.
- Caller intent
- Application selects structure
- Typed query factory
- Field-type mismatch
- Data-owner boundary
- Factory gate pending
- Intent-gate denial was established
The expected decision is REJECT with reason=field-type. No accepted filter or statement is prepared and queries=0 remains.
- Caller intent
- Decision: REJECT
- Typed query factory
- Queries=0
- Data-owner boundary
- Rows=null
- No dispatch reached the data owner
statementId=null and filter=null. The driver or document matcher is not described as executing input and then concealing a result.
- Caller intent
- No accepted query structure
- Typed query factory
- No data-owner call
- Data-owner boundary
- No result delivery
- Failure and no-match were separated
rows=null represents intent failure. The acceptedNoMatch search for Bread has ACCEPT, queries=1 and rows=[]; an empty result requires a performed read.
- Caller intent
- Rejection: rows=null
- Typed query factory
- Accepted no-match: rows=[]
- Data-owner boundary
- Outcomes distinct
- The denial class was not expanded incorrectly
These ordinary type/sort denials show a boundary contract. They are not executable injection, endpoint-vulnerability or actor-permission-failure outcomes.
- Caller intent
- Contract rejection known
- Typed query factory
- No query execution
- Data-owner boundary
- Permission separate
Sort unknown
- The query-intent profile was read
The SQL backend allows only id or title for sort. The unknown-sort input has sort=rating; the client choice is compared with the approved statement set.
- Caller intent
- Backend: sql
- Typed query factory
- Intent validation pending
- Data-owner boundary
- Query not executed
- Value and structure responsibilities were separated
A sort choice is not solved by binding an identifier as a value parameter. The application maps id/title to two fixed statements without creating new SQL for rating.
- Caller intent
- Application selects structure
- Typed query factory
- Sort outside enum
- Data-owner boundary
- Factory gate pending
- Intent-gate denial was established
The expected decision is REJECT with reason=unknown-sort. With no approved statement selected, queries=0 remains; the result is not presented as an empty list.
- Caller intent
- Decision: REJECT
- Typed query factory
- Queries=0
- Data-owner boundary
- Rows=null
- No dispatch reached the data owner
statementId=null and filter=null. The driver or document matcher is not described as executing input and then concealing a result.
- Caller intent
- No accepted query structure
- Typed query factory
- No data-owner call
- Data-owner boundary
- No result delivery
- Failure and no-match were separated
rows=null represents intent failure. The acceptedNoMatch search for Bread has ACCEPT, queries=1 and rows=[]; an empty result requires a performed read.
- Caller intent
- Rejection: rows=null
- Typed query factory
- Accepted no-match: rows=[]
- Data-owner boundary
- Outcomes distinct
- The denial class was not expanded incorrectly
These ordinary type/sort denials show a boundary contract. They are not executable injection, endpoint-vulnerability or actor-permission-failure outcomes.
- Caller intent
- Contract rejection known
- Typed query factory
- No query execution
- Data-owner boundary
- Permission separate
ASSUMPTION · BOUNDARY · CONTROL
The review question is when a value became structure
Begin a review with responsibility rather than the visual appearance of input: where does the application choose the statement/filter, which fields can the caller influence, and which typed values actually reach the driver? Ordinary O'Reilly shows that punctuation can remain business data. It is not an attack-string exercise. The fixed SELECT and separate tuple expose which code owns the query structure.
Knowing that an ORM is used does not establish the contract of every call path. A model method, raw-expression escape hatch, and sort/projection factory may have different contracts. No raw expression executes in this lesson; the review asks whether the approved factory is used. Statement identifier, validated field types, and selected scope make useful review evidence. Dumping every input value into logs can disclose data without helping establish those boundaries.
Parsed objects and admitted filters are separate states
When title receives the ordinary display object, the transition from parsed JSON to business type stops. No query reaches the data owner and an earlier row list cannot masquerade as new success. This inert failure teaches the difference between parser acceptance and operation admission. Query count=0 together with a null result is the evidence; an error message by itself is insufficient.
An empty result needs evidence that an accepted lookup actually ran. If unknown sort rating were represented by the same empty list, contract failure would disappear. Missing evidence must not silently become denial or permission either. These narrow checks involve no endpoint discovery, injection payload, authorization bypass, or vulnerability reproduction on another system. They demonstrate the facts a boundary review can legitimately rely on.
3. Blue · Secure development & defense
Read every scenario without motion
Value binding
- The normal public intent was selected
The normal SQL input has title=O'Reilly and sort=id. Punctuation belongs to business data and is not used to select query syntax.
- Intent/query observation
- Title: O'Reilly
- Factory/result comparison
- Sort: id
- Evidence conclusion
- Factory review pending
- The fixed-statement identity was matched
sort=id selects the by-id statement. It uses title=? and visibility=?; title and public scope remain separate bound values.
- Intent/query observation
- Statement: by-id
- Factory/result comparison
- Values: O'Reilly / public
- Evidence conclusion
- Execution pending
- The expected query result was read
The normal expected record has queries=1 and rows=[{id:2,title:O'Reilly}]. This positive outcome preserves punctuation as a value without validating every query path.
- Intent/query observation
- Queries=1
- Factory/result comparison
- Row id=2
- Evidence conclusion
- Title preserved
- Response-projection fields were separated
The profile projection is only id and title. Visibility is used as a public-scope condition; copying every stored field into the response is not required.
- Intent/query observation
- Scope: public
- Factory/result comparison
- Projection: id/title
- Evidence conclusion
- No extra stored fields
- The document-branch factory was compared
For document input title=Tea, the application creates an equality filter {title:Tea,visibility:public}. Building a new filter from an accepted string differs from copying the caller object.
- Intent/query observation
- SQL fixed statement
- Factory/result comparison
- Document fixed equality filter
- Evidence conclusion
- Factories explicit
- Binding was not converted into permission
Parameter binding helps preserve the value/structure boundary. Tenant authorization, database-account permissions and production query timeout require separate controls beyond this public fixture.
- Intent/query observation
- Value boundary known
- Factory/result comparison
- Public scope fixed
- Evidence conclusion
- Authorization separate
No-match / missing
- The accepted-no-match record was opened
acceptedNoMatch for title=Bread declares decision=ACCEPT and queries=1. rows=[] means the search was accepted and performed but found no match.
- Intent/query observation
- Intent accepted
- Factory/result comparison
- Queries=1
- Evidence conclusion
- Rows=[]
- Missing input was treated as a different tuple
missing.input=null has decision=NOT-COMPUTED, queries=0 and rows=null. With no search performed, it is not interpreted like Bread’s empty result.
- Intent/query observation
- Input=null
- Factory/result comparison
- Queries=0
- Evidence conclusion
- Rows=null
- Known rejection and missing input were separated
wrong-type and unknown-sort have REJECT while missing has NOT-COMPUTED. All can have no query, but their evaluated contract conditions differ.
- Intent/query observation
- Known rejection distinct
- Factory/result comparison
- Missing intent unresolved
- Evidence conclusion
- No-query cause explicit
- Query count was linked with results
An accepted match needs queries=1 and projected rows; no-match expects the same query count with []. A UI error or empty view alone cannot complete this tuple.
- Intent/query observation
- Intent + query + rows
- Factory/result comparison
- UI alone insufficient
- Evidence conclusion
- Evidence tuple required
- The document result was read narrowly
The Tea equality filter returns projected rows for id=1 and id=3. This literal-matcher result does not observe actual MongoDB/ODM behavior or every filter operator.
- Intent/query observation
- Document rows: 1, 3
- Factory/result comparison
- Literal matcher scope
- Evidence conclusion
- ODM runtime unobserved
- Diagnosis did not require excessive value logging
Statement identifier, field types, scope and outcome can suffice for review. Logging every input value does not strengthen permission or query-correctness evidence and may create unnecessary disclosure.
- Intent/query observation
- Bounded decision metadata
- Factory/result comparison
- No-match vs missing retained
- Evidence conclusion
- No excessive value log
IMPLEMENTATION · SECURITY CONTROL · VERIFICATION
Keep the query factory small and explicit
The public catalog factory validates a closed field set, scalar title, and sort enum. Admitted id/title choices map only to two fixed SELECT statements. Values reach sqlite3 as a tuple; the document branch constructs a fresh application-owned equality filter. Copying a client object is not validation by itself. Type and allowed-field decisions complete before the builder emits its query contract.
Checks should measure behavior beyond restating the source shape: observe native SQLite returning the correct row for an ordinary punctuation title, an unchanged seed row count afterward, and an unknown sort that never increments execute count. For the document matcher, compare wrong-type rejection before lookup with an admitted match returning only projected fields. Driver or framework changes need fresh evaluation; one SQLite result does not generalize to every engine and ODM.
Record failure, scope, and evidence separately
Admissible intent, selected statement/filter, query count, and projected result are distinct fields. Rejection produces a null result; an accepted lookup without a match produces []. Missing intent is NOT-COMPUTED. That small distinction matters during incident review: permission decisions, database connectivity failures, and genuinely absent resources should not become one indistinguishable record. Public visibility is the example’s fixed scope; it does not claim tenant authorization.
A database account should have only the privileges its operation needs; a read-only catalog provides no reason for admin privileges. Production authorization, query timeouts, row/page limits, error disclosure, and transaction policy require additional design. The artifact does not claim to have tested them. Observed native evidence is limited to its own memory database, ordinary seeds, fixed-statement binding, and a narrow literal matcher. Reduced motion preserves the same readable decisions in the interface.
NOW APPLY IT TO THE CASE
When must accepted JSON stop before becoming a query?
The application selects fixed SQL or equality filters from closed intent and a sort enum. Harmless title values remain data; denial, no-match and missing input have distinct tuples.
Compare the cached case representation with its authoritative data owner. Keep stored value, revision, transaction and cache-copy completion separate.
Open the related case file ↗READ THE EXAMPLE TOGETHER
Atlas query contract and public result ledger
Select a field and examine what it supports and where the evidence ends.
Read the raw artifact and field explanations
{
"profile": {
"allowedKeys": [
"backend",
"title",
"sort"
],
"backends": [
"sql",
"document"
],
"titleCodePoints": [
1,
40
],
"sortChoices": [
"id",
"title"
],
"scope": "public",
"projection": [
"id",
"title"
]
},
"seed": [
{
"id": 1,
"title": "Tea",
"visibility": "public"
},
{
"id": 2,
"title": "O'Reilly",
"visibility": "public"
},
{
"id": 3,
"title": "Tea",
"visibility": "public"
}
],
"statements": {
"by-id": "SELECT id, title FROM books WHERE title = ? AND visibility = ? ORDER BY id",
"by-title": "SELECT id, title FROM books WHERE title = ? AND visibility = ? ORDER BY title, id"
},
"inputs": {
"normal": {
"backend": "sql",
"title": "O'Reilly",
"sort": "id"
},
"document": {
"backend": "document",
"title": "Tea",
"sort": "id"
},
"wrong-type": {
"backend": "document",
"title": {
"display": "Tea"
},
"sort": "id"
},
"unknown-sort": {
"backend": "sql",
"title": "Tea",
"sort": "rating"
}
},
"expected": {
"normal": {
"decision": "ACCEPT",
"reason": "public-exact-match",
"queries": 1,
"statementId": "by-id",
"filter": null,
"rows": [
{
"id": 2,
"title": "O'Reilly"
}
]
},
"document": {
"decision": "ACCEPT",
"reason": "public-exact-match",
"queries": 1,
"statementId": null,
"filter": {
"title": "Tea",
"visibility": "public"
},
"rows": [
{
"id": 1,
"title": "Tea"
},
{
"id": 3,
"title": "Tea"
}
]
},
"wrong-type": {
"decision": "REJECT",
"reason": "field-type",
"queries": 0,
"statementId": null,
"filter": null,
"rows": null
},
"unknown-sort": {
"decision": "REJECT",
"reason": "unknown-sort",
"queries": 0,
"statementId": null,
"filter": null,
"rows": null
}
},
"missing": {
"input": null,
"decision": "NOT-COMPUTED",
"queries": 0,
"rows": null
},
"acceptedNoMatch": {
"title": "Bread",
"decision": "ACCEPT",
"queries": 1,
"rows": []
}
}- inputs
- Ordinary typed intent; not client-authored query syntax.
- statements / filter
- Application-owned structure and fixed public scope.
- queries
- Completed lookup count; zero on rejection.
- rows
- Completed projection: no-result null differs from accepted-no-match [].
Own Python SQLite :memory: bound SELECT and a local Python literal-document matcher. No MongoDB server/driver/cursor, network, ORM, arbitrary query, exploit, or user authorization executes.
Two reality checks
Every bound value can select a column or sort.
Value slots do not choose structure; sort/identifiers require approved application mappings.
A query using a JSON object is automatically safe.
Field/type contracts and an application-owned filter are required; parsing is not authorization.
YOUR TURN
Work through this story.
Predict the outcome, find the evidence boundary, and explain it in your own words.
For the document backend, title is an object {display: Tea}, not a string. Does a query run?
- No. field-type with queries=0 and filter=null.
- Yes. Every JSON object automatically becomes a database filter.
Read the explained answer
No. field-type with queries=0 and filter=null. — This contract requires a title string. The input object is not directly used as a query operator or filter.
Why does the O'Reilly title remain match data rather than SQL statement source?
Selected lesson artifact fields; teaching example
- inputs.normal
- {"backend":"sql","title":"O'Reilly","sort":"id"}
- statements.by-id
- SELECT id, title FROM books WHERE title = ? AND visibility = ? ORDER BY id
- expected.normal
- {"decision":"ACCEPT","reason":"public-exact-match","queries":1,"statementId":"by-id","filter":null,"rows":[{"id":2,"title":"O'Reilly"}]}
- The apostrophe in the title creates new statement structure.
- The statement is fixed; title is bound as data to a placeholder.
Read the explained answer
The statement is fixed; title is bound as data to a placeholder. — The by-id statement receives title and visibility through ? placeholders. The result is public id=2; the title does not choose query grammar.
sort=rating has queries=0, while acceptedNoMatch has queries=1 and rows=[]. Why are these different, and is query/data separation authorization?
Read the explained answer
Unknown sort is outside the id/title choices, so it is rejected before querying and rows stays null. Bread is accepted, queried, and returns rows=[] because no record matches. The server selects fixed query structure; parameterization does not replace object/tenant permission. This profile uses a public projection, and document-adapter results are not a real MongoDB deployment test.
□ I separated rejection from a successful no-match result.
□ I did not treat parameterization as resource authorization.
Open the application console · request / response / browser
Also open request, URL, cookie and DOM examples
Sources and scope
- OWASP — SQL Injection Prevention Cheat SheetDefense Option 1: Prepared Statements (with Parameterized Queries) · Defense Option 3: Allow-list Input Validation · Least Privilege
- Python — sqlite3How to use placeholders to bind values in SQL queries · sqlite3.connect; :memory: database
- OWASP — NoSQL Security Cheat SheetPrevent NoSQL Injection · Use Secure Driver / ODM Patterns · Authentication & Authorization
- MongoDB — Read DocumentsQuery Result Format; find returns a cursor