Skip to main content

j-perm plugin for SQL

Project description

j-perm-sql

A j-perm plugin that builds and executes SQL SELECT queries from j-perm constructs.

  • SQL is described with a tree of $-constructs ($select, $col, $val, predicates, joins, …).
  • A single new top-level operation — op: sql — renders that tree to a parameterized (sql, params) pair and hands it to a configurable executor (any ORM's raw-execute function).
  • The SQL constructs live in an isolated named pipeline: they mean nothing outside op: sql. {"$select": …} used as an ordinary value is just a dict.

v1 scope: the full standard SELECT surface (read-only). DDL/DML (CREATE/ALTER/INSERT/UPDATE/DELETE) is intentionally out of scope.

Install

pip install j-perm-sql

Requires j-perm >= 1.9.0 (the version that made run_pipeline a passthrough invoker, which this plugin relies on).

Quick start

from j_perm import build_default_engine
from j_perm_sql import install_sql

def run_sql(sql, params):
    # any ORM's raw execute: cursor.execute(sql, params); return rows
    ...

engine = build_default_engine()
install_sql(engine, run_sql, paramstyle="qmark")

engine.apply(
    {"op": "sql", "to": "/rows", "query": {"$select": {
        "columns": [{"$col": {"name": "id"}}, {"$col": {"name": "name"}}],
        "from": {"table": "users"},
        "where": {"$gte": [{"$col": {"name": "age"}}, {"$val": 18}]},
        "order_by": [{"expr": {"$col": {"name": "name"}}}],
        "limit": 50,
    }}},
    source={}, dest={},
)
# run_sql receives:  ('SELECT "id", "name" FROM "users" WHERE "age" >= ? ORDER BY "name" LIMIT 50', [18])
# result is written to dest at /rows

install_sql patches an existing engine — it registers the isolated SQL pipeline and the op: sql operation. It composes with any engine and any other plugins.

The op: sql operation

{"op": "sql", "query": <SQL construct tree>, "to": "/dest/path"}
  • query — the SQL construct tree.
  • to — optional destination pointer (template-expanded); the executor's result is written there. If omitted, the result is discarded.

Parameterization & injection safety

Data values are always bound as parameters, never interpolated:

  • $val (and the data sides of $in, $between, $values) emit a placeholder and add the value to params.
  • Identifiers (table/column/alias names) are validated against a conservative charset and quoted.
  • Function names, CAST types, join types, sort directions, etc. are validated against whitelists.
{"$eq": [{"$col": {"name": "name"}}, {"$val": {"$ref": "/user_input"}}]}
# → '"name" = ?'   with the (possibly malicious) value safely in params

Inside $val, the value expression is resolved with j-perm's normal value pipeline, so $ref, ${…} templates, and @: dest-pointers all work.

Dialect / RenderOptions

Everything that genuinely differs between databases is configurable:

from j_perm_sql import RenderOptions

install_sql(engine, run_sql, dialect=RenderOptions(
    paramstyle="numeric",      # qmark (?) | format (%s) | numeric ($1) | named (:p1)
    identifier_quote='"',      # e.g. "`" for MySQL
    pagination="fetch",        # "limit" (LIMIT n OFFSET m) | "fetch" (OFFSET m ROWS FETCH FIRST n ROWS ONLY)
    concat_operator="||",      # "||" or "+"
))

Sync & async

install_sql inspects the executor: a coroutine function registers the async handler (use with engine.apply_async); a regular function registers the sync handler (use with engine.apply).

async def run_sql(sql, params): ...
install_sql(engine, run_sql)                  # async
await engine.apply_async(spec, source=, dest=)

Construct reference

Query

Construct Form
$select {with?, distinct?, columns?, from?, joins?, where?, group_by?, having?, order_by?, limit?/offset? | fetch?}
$union / $union_all / $intersect / $except {"$union": [q1, q2, …], order_by?, limit?…}
$values {"$values": [[…row…], …]} (table source or IN)

Projection / expressions

Construct Renders
$col "t"."name" [AS "alias"]; "id"; *; "t".*
$val a bound parameter
$func / $call NAME([DISTINCT ]args)[ OVER (…)][ AS "alias"] (use "*" for COUNT(*))
$cast CAST(expr AS TYPE)
$case searched CASE WHEN … THEN … [ELSE …] END
$concat `(a
$add $sub $mul $div $mod (a op b …)

Projection items may also be {"expr": <operand>, "as": "alias"}.

Predicates (WHERE / HAVING / ON)

$and $or $not · $eq $ne $gt $gte $lt $lte · $in/$not_in (list or subquery) · $between/$not_between · $like/$not_like (+ escape) · $is_null/$is_not_null · $exists/$not_exists · $any/$all/$some (quantified).

FROM / JOINtable source = a table name (string), a {table, as?, schema?} dict, a nested $select/$values (derived table, needs as), or lateral: true. $join: {type, table, as?, on? | using?, natural?} with type inner/left/right/full/cross.

Windowsover on $func: {partition_by?, order_by?, frame?} where frame is {type: "rows"|"range", start, end?} and a bound is "unbounded preceding" | "unbounded following" | "current row" | {preceding: n} | {following: n}.

GROUP BY — a list of expressions, or {"$rollup": […]} / {"$cube": […]} / {"$grouping_sets": [[…], …]}.

CTEwith: [{name, columns?, recursive?, query: $select}].

See tests/ for end-to-end examples.

Portability caveats

The DSL renders standard SQL and does not validate that a target database supports every feature — portability is the query author's responsibility:

  • LIMIT/OFFSET vs OFFSET/FETCH, RIGHT/FULL JOIN, INTERSECT/EXCEPT, NATURAL JOIN, NULLS FIRST/LAST, LATERAL, GROUPING SETS/ROLLUP/CUBE, and the concatenation operator (|| vs +) are not universal.
  • CTEs and window functions are standard but require recent versions (e.g. MySQL ≥ 8, SQLite ≥ 3.25 for windows / ≥ 3.8.3 for CTEs).

Use RenderOptions to match the target dialect's placeholder style, identifier quoting, pagination form, and concatenation operator.

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

j_perm_sql-0.1.0.tar.gz (17.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

j_perm_sql-0.1.0-py3-none-any.whl (17.5 kB view details)

Uploaded Python 3

File details

Details for the file j_perm_sql-0.1.0.tar.gz.

File metadata

  • Download URL: j_perm_sql-0.1.0.tar.gz
  • Upload date:
  • Size: 17.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.11.0

File hashes

Hashes for j_perm_sql-0.1.0.tar.gz
Algorithm Hash digest
SHA256 3f30e88b66764ae6afff5501d3b4fe2bb1aa39734752b53ebb360965e8e2db86
MD5 5ac612bf32ffe1e6fb45846c18bdd3b8
BLAKE2b-256 a6ef1a29f8371d40b7582e4b412c2d3e041630c374a3052c32357947bbd3b81f

See more details on using hashes here.

File details

Details for the file j_perm_sql-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: j_perm_sql-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 17.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.11.0

File hashes

Hashes for j_perm_sql-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 569ecac7e74ab10097a7aef65c92bc1ccab8f77f3af19681dfd2d538a4b212a2
MD5 5d3a3efb81ef8b674987be9a7e098478
BLAKE2b-256 c290a1ce0387e156fe4b740f11dcd042994fd9ab28cffe1ef1403907406fbf3c

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page