Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

10 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

abap-sql

tests npm

Prepare ABAP Open SQL for SAP's ADT data-preview console. Fixes the dialect, enforces the limit.

These three statements fail on a live system:

Sent Response
SELECT SUM(netwr) FROM vbap 400 Unknown column name "SUM(NETWR)"
SELECT COUNT(DISTINCT kunnr) FROM vbak 400 ")" is invalid here (due to grammar)
SELECT a FROM t WHERE (x = '1' OR y = '2') 400 ") " was expected here.

Three messages, three apparent rules, one actual cause: the parser wants a space on the inside of every parenthesis. It even says so in the third message. All three pass once spaced.

This package knows that rule, and the handful of others that cost people afternoons.

npm install abap-sql

Driving an agent rather than writing code? The same rules ship as an MCP server: sap-abap-sql-mcp.

Why this endpoint matters

The data-preview console (/sap/bc/adt/datapreview/freestyle) is the widest read surface an on-premise ABAP system exposes over HTTP. It reaches any table, needs no database user, and is where every ADT client and agent bridge ends up. It is also strict in ways its error messages do not explain.

Reaching one from outside the network is a separate problem, and a worse one: SICF nodes, service registration, system versus dialog users, and a tunnel that opens no inbound port. There is a free write-up of what actually works: connecting an on-premise S/4HANA over HTTP.

Use

import { prepare, AbapSqlError } from "abap-sql";

prepare("SELECT COUNT(DISTINCT kunnr) FROM vbak WHERE vtweg = '10'");
// → "SELECT COUNT( DISTINCT kunnr ) FROM vbak WHERE vtweg = '10'"

prepare("SELECT vbeln FROM vbak ORDER BY vbeln DESC");
// throws AbapSqlError: Sorting is ORDER BY x DESCENDING, not DESC.

prepare runs the checks, applies what is mechanical, and refuses what is not. Lower-level pieces are exported too: normalize, lint, isReadOnly, tokenize.

What it fixes for you

Spacing inside parentheses, everywhere it is needed at once: aggregates (SUM( netwr )), DISTINCT (COUNT( DISTINCT kunnr )), subqueries (IN ( SELECT … )), and boolean groups (( a OR b )). COUNT(*) is left compact, because the parser takes it either way and every character counts against the ceiling.

What it refuses, with the correction

Written Told
LIMIT 10 ABAP Open SQL has no LIMIT. Cap the result with the reader's row limit instead.
ORDER BY a DESC Sorting is ORDER BY x DESCENDING, not DESC.
p.vbeln Qualify join fields with ~, not a dot: write p~vbeln, not p.vbeln.
DELETE FROM … Only SELECT is allowed.

Each returns a stable rule id, so you can suppress or test one of them.

The 255-character ceiling

The console rejects 256 characters and accepts 255, measured across the whole statement with newlines collapsed. Wrapping buys nothing: the server's message names "line 1", but a 300-character statement across five short lines fails identically.

prepare measures after normalizing, because spacing lengthens the statement. Checking the input instead lets a statement pass locally and fail on the system.

Why a lexer and not a regex

Because a parenthesis inside 'PUMP (SPARE)' is data, and rewriting it changes the answer rather than the shape. ABAP has two string forms, an escape, and a comment form, and all four have to be understood before a single character is moved:

normalize("SELECT a FROM t WHERE x = 'PUMP (SPARE)'");   // unchanged
normalize("SELECT a FROM t WHERE x = `PUMP (SPARE)`");   // unchanged, backtick form
normalize("SELECT a FROM t WHERE x = 'it''s'");        // unchanged, doubled quote is an escape
normalize("SELECT a FROM t WHERE x = 'a  b'");         // inner spaces survive the collapse

The same reasoning applies to the read-only check. A scan of the raw text for write verbs refuses this, which is a read:

isReadOnly("SELECT belnr FROM bkpf WHERE bktxt = 'Update pending'");  // true

Tokenizing is done by abaplint's lexer, which is the one correct ABAP tokenizer in TypeScript. It is reached at a private path because the package does not export it from the root; test/contract.test.ts asserts every assumption we make about it against the installed copy, so an upstream move fails a test here rather than corrupting a query quietly.

Scope

This prepares statements for the ADT data-preview console. It does not parse responses, hold a connection, or know anything about your system. Pair it with whatever ADT client you already have.

Open SQL written inside an ABAP program is a related but different dialect: it has INTO TABLE, UP TO n ROWS, @host variables, and no character ceiling. The paren rule and the sort keyword still apply there; the ceiling does not, so pass prepare(sql, { maxChars: Infinity }) if you are working on program source.

Releasing

git tag v0.1.1 && git push --tags. CI checks the tag against package.json, runs the tests and the Node import check, then publishes to npm.

Publishing uses npm trusted publishing, so there is no token in this repo and nothing to rotate. npm trusts this repository and this workflow through OIDC, and provenance is attested automatically.

Credits

Tokenization by abaplint (MIT). The rules come from statements that actually failed against a live on-premise system, with the system's own error text recorded next to each fix.

Built by Daslab. We connect agents to on-premise ERP systems.

License

MIT

About

Prepare ABAP Open SQL for SAP's ADT data-preview console. Fixes the dialect, enforces the limit.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages