feat(sql): add dialect-aware SQL escape helper#8
Open
MiroCillik wants to merge 13 commits intomainfrom
Open
Conversation
Moves the design spec and bite-sized implementation plan into this repo alongside the feature they describe. Previously lived in connection-docs while brainstorming, but they document SDK work so the SDK repo is the right home. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This was referenced Apr 22, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds a dialect-aware SQL escape helper to the Keboola Query Service Python SDK so users can safely interpolate untrusted values into raw SQL. Addresses the SQL injection gap flagged in the Storage Access docs review (PR keboola/connection-docs#910).
New public API under
from keboola_query_service import SQL:SQL(dialect)— factory bound to"snowflake"or"bigquery"(validates at construction)sql.literal(value)— escapesNone/bool/int/float/str/date/datetime/list/tuple; rejects unknown types with a clear message suggestingstr(value)sql.ident(*parts)— quoted identifier joined with., dots inside a part are preserved (so"in.c-main"."customers"works)sql.date(value)— explicitDATEliteral fromdateor"YYYY-MM-DD"stringsql.raw(s)— reviewed escape hatch for pre-formed SQL fragments (CURRENT_TIMESTAMP, function calls)sql.format(template, **values)—str.format-style named interpolation, every value routed throughliteral()unless alreadySafeSqlUsage:
Correctness decisions worth noting
\→\\). Snowflake interprets backslash sequences inside single-quoted literals (\n,\t,\\…), so a naive escape that only doubled single quotes would corrupt round-trips for any string containing\. Covered by a dedicated regression test.booldispatched beforeint—isinstance(True, int)isTrue, so the order matters. Regression test inTestLiteralBoolBeforeInt.(NULL), not()—IN ()is a syntax error;IN (NULL)returns no rows (semantically correct for an empty set).TIMESTAMP_NTZ/DATETIME; tz-aware emitTIMESTAMP_TZ/TIMESTAMPwith the original offset.format_specrejected —sql.format("{p:.2f}", p=1.2345)raisesValueErrorrather than silently dropping the format spec.{0}/{}raiseIndexError(named-only in v1).Cross-SDK byte-equality
This PR pairs with keboola/query-service-api-js-sdk#3. Both produce byte-identical output for the docs example (verified: same 101-byte string, same MD5).
Specs and plan
Now live in this repo alongside the feature they describe:
docs/superpowers/specs/2026-04-21-sdk-quote-helper-design.mddocs/superpowers/plans/2026-04-21-sdk-quote-helper-implementation.mdTest plan
tests/test_sql.py— full suite passes (pytest tests/ -v→ 109 passed)ruff checkclean onsrc/and new test file (pre-existingtests/test_client.pyissues are unrelated to this PR)mypystrict clean onsrc/keboola_query_service/', string with\, date, tz-aware datetime, IN list,sql.raw("CURRENT_TIMESTAMP")) and confirm the backslash-containing string round-trips byte-for-byte0.1.7),_version.py, and README snippet🤖 Generated with Claude Code