@chaprola/mcp-server 1.6.4 → 1.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -44,3 +44,14 @@ For COUNT: `"value": "department", "aggregate": "count"`
44
44
  SQL equivalent: `SELECT department, year, SUM(revenue) FROM sales GROUP BY department, year`
45
45
 
46
46
  Row and column totals are included automatically in the response.
47
+
48
+ ## TABULATE in Programs
49
+
50
+ The `/query` pivot feature is also available in the Chaprola language via the TABULATE command:
51
+
52
+ ```chaprola
53
+ TABULATE SALES SUM revenue FOR department VS year WHERE year GE "2020" INTO trends
54
+ PRINT TABULATE trends AS CSV
55
+ ```
56
+
57
+ TABULATE produces a matrix in memory — same cross-tabulation as `/query` pivot, but executed inside a program with dynamic PARAM values and chaining with QUERY results.
@@ -1,8 +1,16 @@
1
1
  # Chaprola Programs (.CS Source)
2
2
 
3
3
  ## Compile & Run
4
+
5
+ **Best practice:** Start every program with `OPEN PRIMARY "filename" 0`. The compiler reads the format from the OPEN PRIMARY statement — no `primary_format` parameter needed. This makes programs self-documenting and eliminates compile-time guessing.
6
+
4
7
  ```bash
5
- POST /compile {userid, project, name: "REPORT", source: "...", primary_format: "STAFF", secondary_format?: "DEPTS"}
8
+ # Preferred: source declares its own primary file
9
+ POST /compile {userid, project, name: "REPORT", source: "OPEN PRIMARY \"STAFF\" 0\n..."}
10
+
11
+ # Legacy: pass primary_format explicitly (still works, but OPEN PRIMARY is better)
12
+ POST /compile {userid, project, name: "REPORT", source: "...", primary_format: "STAFF"}
13
+
6
14
  POST /run {userid, project, name: "REPORT", primary_file: "STAFF", record: 1, async?: true, nophi?: true}
7
15
  POST /run/status {userid, project, job_id} # poll async jobs
8
16
  POST /publish {userid, project, name, primary_file, acl?: "public|authenticated|owner|token"}
@@ -12,33 +20,53 @@ POST /publish {userid, project, name, primary_file, acl?: "public|authenticated|
12
20
 
13
21
  | Prefix | Description |
14
22
  |--------|-------------|
15
- | `P` | Primary data file (current record) |
16
- | `S` | Secondary data file (current record) |
17
- | `U` | User buffer (output scratch) |
18
- | `X` | System text (date, time, filenames) |
23
+ | `P` | Primary data file access fields by name: `P.salary`, `P.last_name` |
24
+ | `S` | Secondary data file access fields by name: `S.dept`, `S.emp_id` |
25
+ | `U` | User buffer (scratch for intermediate data) |
26
+ | `X` | System text access by property name: `X.username`, `X.utc_time`, `X.record_num` |
27
+
28
+ ### System Text Properties (X.)
29
+
30
+ | Property | Description |
31
+ |----------|-------------|
32
+ | `X.year` | Year (four digits) |
33
+ | `X.julian` | Julian date (1–366) |
34
+ | `X.hour` | Hour (military time, 0–23) |
35
+ | `X.minute` | Minute (0–59) |
36
+ | `X.username` | Authenticated user (first 10 chars) |
37
+ | `X.record_num` | Record number of primary file |
38
+ | `X.display_file` | Display filename |
39
+ | `X.data_file` | Data filename |
40
+ | `X.proc_file` | Procedure filename |
41
+ | `X.utc_time` | UTC datetime (ISO 8601, 20 chars) |
42
+ | `X.elapsed` | Elapsed execution time (SSSSS.CC, 9 chars) |
43
+ | `X.primary_modified` | Primary file Last-Modified (ISO 8601, 20 chars) |
44
+ | `X.secondary_modified` | Secondary file Last-Modified (ISO 8601, 20 chars) |
19
45
 
20
46
  ## Language Essentials
21
47
 
22
48
  ```chaprola
23
- // Loop through records
49
+ // Loop through records and print with concatenation
24
50
  DEFINE VARIABLE rec R41
25
51
  LET rec = 1
26
52
  100 SEEK rec
27
53
  IF EOF GOTO 900
28
- MOVE P.name U.1 20 // copy field to output buffer
29
- GET sal FROM P.salary // numeric field R variable
30
- PUT sal INTO U.22 10 D 2 // R variable → formatted output
31
- PRINT 0 // output full U buffer, clear it
54
+ GET sal FROM P.salary
55
+ PRINT P.name + " — " + P.department + " $" + sal
32
56
  LET rec = rec + 1
33
57
  GOTO 100
34
58
  900 END
35
59
  ```
36
60
 
37
- - `PRINT 0` output entire U buffer and clear. `PRINT N` — output exactly N chars.
38
- - `MOVE BLANKS U.1 80` — clear a region. `MOVE "literal" U.1 7` — move literal.
39
- - `IF EQUAL "text" U.50 4 GOTO 200` — compare literal to memory location.
40
- - `U.name` — named positions (auto-allocated by compiler): `MOVE P.name U.name 20`
41
- - `DEFINE VARIABLE counter R41` — alias R-variable. **Use R41-R50** (R1-R40 reserved for HULDRA).
61
+ - **PRINT concatenation (preferred):** `PRINT P.name + " earns " + R41` — auto-trims fields, auto-formats numbers, auto-flushes.
62
+ - `PRINT P.fieldname` — output a single field (auto-flush, auto-trim).
63
+ - `PRINT "literal"` — output a literal string (auto-flush).
64
+ - `PRINT 0` — output entire U buffer (less common, for columnar reports).
65
+ - `CLEAR U` — clear entire user buffer. `CLEAR P` / `CLEAR S` — clear primary/secondary region.
66
+ - `MOVE BLANKS P.notes` — clear a single field (length auto-filled).
67
+ - `MOVE "Active" P.status` — write literal to field (auto-padded to field width).
68
+ - `IF EQUAL "text" P.status GOTO 200` — compare literal to field.
69
+ - `DEFINE VARIABLE counter R41` — alias R-variable. **Use R41-R99** (R1-R40 reserved for HULDRA).
42
70
 
43
71
  ## PUT Format Codes
44
72
 
@@ -49,7 +77,7 @@ LET rec = 1
49
77
  | `I` | Integer (right-justified) | ` 1234` |
50
78
  | `E` | Scientific notation | `1.23E+03` |
51
79
 
52
- Syntax: `PUT R41 INTO U.30 10 D 2` — (R-var, location, width, format, decimals)
80
+ Syntax: `PUT R41 INTO P.salary D 2` — (R-var, location, width auto-filled from field name, format, decimals)
53
81
 
54
82
  ## Math
55
83
 
@@ -59,14 +87,24 @@ LET R43 = EXP R41 // also: LOG, SQRT, ABS
59
87
  LET R44 = POW R41 R42 // R41^R42
60
88
  ```
61
89
 
90
+ ## Date Arithmetic
91
+
92
+ ```chaprola
93
+ GET DATE R41 FROM X.primary_modified // parse timestamp → epoch seconds
94
+ GET DATE R42 FROM X.utc_time // current UTC time
95
+ LET R43 = R42 - R41 // difference in seconds
96
+ LET R43 = R43 / 86400 // convert to days
97
+ PUT DATE R42 INTO U.1 20 // write epoch as ISO 8601 string
98
+ ```
99
+
62
100
  ## Secondary Files (FIND/JOIN)
63
101
 
64
102
  ```chaprola
65
103
  OPEN "DEPARTMENTS" 0 // open secondary file
66
- FIND match FROM S.dept_code 3 USING P.dept_code
104
+ FIND match FROM S.dept_code USING P.dept_code
67
105
  IF match EQ 0 GOTO 200 // 0 = no match
68
106
  READ match // load matched record
69
- MOVE S.dept_name U.30 15
107
+ PRINT P.name + " — " + S.dept_name
70
108
  WRITE match // write back if modified
71
109
  CLOSE // flush + close
72
110
  ```
@@ -81,5 +119,74 @@ LET lvl = PARAM.level // numeric param → R variable
81
119
  Publish, then call: `POST /report?userid=X&project=Y&name=Z&deck=kanji&level=3`
82
120
  Discover params: `POST /report/params {userid, project, name}`
83
121
 
122
+ ## QUERY Command
123
+
124
+ QUERY filters, selects, and reorganizes data inside a Chaprola program — the same power as the `/query` API endpoint, but as a language command.
125
+
126
+ **Output is a .QR file (read-only snapshot).** Cannot be modified with INSERT, UPDATE, or DELETE. Use the original .DA file for writes. R20 is set to the number of matched records.
127
+
128
+ ```chaprola
129
+ // Filter + column select
130
+ QUERY STAFF FIELDS name, salary INTO HIGH_PAID WHERE salary GT 80000
131
+
132
+ // Dynamic WHERE with params and R-variables
133
+ QUERY flashcards INTO results WHERE level EQ PARAM.level
134
+ QUERY data INTO subset WHERE score GE R5 AND category EQ PARAM.type
135
+
136
+ // BETWEEN with dynamic bounds
137
+ QUERY data INTO results WHERE age BETWEEN PARAM.min_age PARAM.max_age
138
+
139
+ // Cross-file filtering (IN/NOT IN) — one per QUERY
140
+ QUERY flashcards INTO new_cards WHERE kanji NOT IN progress.kanji
141
+
142
+ // GROUP BY
143
+ QUERY orders INTO summary WHERE year EQ "2026" GROUP BY region COUNT, SUM total ORDER BY SUM_TOTAL DESC LIMIT 5
144
+
145
+ // FROM syntax (alternative to INTO)
146
+ QUERY results FROM STAFF FIELDS name, salary WHERE dept EQ PARAM.dept
147
+
148
+ // OPEN with WHERE (filter directly into file handle)
149
+ OPEN SECONDARY customers WHERE customer_id IN orders.customer_id
150
+ ```
151
+
152
+ ### QUERY Errors
153
+ - **Missing source file:** FOERR flag set, QUERY skipped. Program can check FOERR and branch. (R20 retains its prior value.)
154
+ - **Missing IN-file:** NOT IN treats as empty set (all records pass). IN treats as empty set (no records pass). This is intentional — a new user with no progress file gets all flashcards.
155
+ - **Missing PARAM:** Silently replaced with blank (string) or 0.0 (numeric). Not a hard error — program continues. Check param warnings in the response for diagnostics.
156
+ - **Zero matches:** Not an error. R20 = 0, output .QR is empty.
157
+
158
+ ### QUERY Limits
159
+ - One index lookup per QUERY (first EQ condition only)
160
+ - One IN/NOT IN per QUERY
161
+ - No nested QUERY — QUERY is a statement, not an expression
162
+ - Output is always a new file — QUERY never modifies the source
163
+ - FIELDS and GROUP BY are mutually exclusive
164
+
165
+ ## TABULATE Command
166
+
167
+ TABULATE produces cross-tabulation matrices inside a program — the language equivalent of `/query` pivot. Result is in-memory only (not written to S3).
168
+
169
+ ```chaprola
170
+ TABULATE sales SUM revenue FOR region VS quarter WHERE year EQ "2026" INTO matrix
171
+ PRINT TABULATE matrix AS CSV // CSV output for charting
172
+ PRINT TABULATE matrix AS JSON // JSON matrix for web apps
173
+ PRINT TABULATE matrix AS TABLE // text table for preview
174
+ ```
175
+
176
+ Aggregates: COUNT, SUM, AVG, MIN, MAX. Multiple aggregates: `TABULATE data COUNT, SUM total FOR row VS col ...`
177
+
178
+ ## File Properties
179
+
180
+ ```chaprola
181
+ LET R1 = orders.RECORDCOUNT // record count of any loaded file
182
+ IF R1 EQ 0 GOTO no_data
183
+ ```
184
+
185
+ ## INDEX Command
186
+
187
+ ```chaprola
188
+ INDEX STAFF ON department // creates STAFF.DEPARTMENT.IDX
189
+ ```
190
+
84
191
  ## Common Field Widths
85
192
  ISO datetime: 20, UUID: 36, email: 50, short ID: 8-12, dollar: 10, phone: 15.
@@ -38,3 +38,54 @@ Types: `inner`, `left`, `right`, `full`. Optional `pre_sorted: true` for merge j
38
38
  - `POST /update-record {userid, project, file, where: [...], set: {field: "value"}}`
39
39
  - `POST /delete-record {userid, project, file, where: [...]}`
40
40
  - `POST /consolidate {userid, project, file}` — merge .MRG into .DA
41
+
42
+ ## QUERY in Programs
43
+
44
+ The QUERY language command does the same thing as the `/query` API but inside a Chaprola program. Use it to filter, select, and reorder data without leaving the runtime.
45
+
46
+ ```chaprola
47
+ // In a program, QUERY replaces /query API calls
48
+ QUERY STAFF FIELDS name, salary INTO TOP_EARNERS WHERE salary GT 80000 ORDER BY salary DESC LIMIT 10
49
+ ```
50
+
51
+ The result is a `.QR` file (read-only snapshot) that can be opened as a secondary file or used in subsequent QUERY commands. R20 is set to the number of matched records. INSERT, UPDATE, and DELETE operations are rejected on .QR files.
52
+
53
+ If the source file doesn't exist, the FOERR flag is set and the QUERY is skipped. If an IN/NOT IN reference file doesn't exist, it's treated as an empty set (NOT IN = all pass, IN = none pass).
54
+
55
+ ## Clustered Sort Columns
56
+
57
+ Import with `sort_columns` to create self-indexing files. The data is physically sorted by the key columns at import time, enabling binary search on the clustered key without a separate .IDX file.
58
+
59
+ ```json
60
+ POST /import {
61
+ "userid": "...", "project": "...", "name": "STAFF",
62
+ "sort_columns": ["department", "name"],
63
+ "data": [...]
64
+ }
65
+ ```
66
+
67
+ - The .F file marks KEY fields (`KEY:1`, `KEY:2`, etc.)
68
+ - QUERY automatically uses binary search on clustered keys
69
+ - No separate .IDX needed for primary access patterns
70
+
71
+ ## split_by on /import
72
+
73
+ Split a dataset into per-group data files at import time. One `.DA` file is created per distinct value of the split field, sharing a single `.F` format file.
74
+
75
+ ```json
76
+ POST /import {
77
+ "userid": "...", "project": "...", "name": "orders",
78
+ "split_by": "region",
79
+ "data": [...]
80
+ }
81
+ ```
82
+
83
+ Produces files like `orders/east.DA`, `orders/west.DA`, etc. Access with dynamic filenames in a program:
84
+
85
+ ```chaprola
86
+ OPEN PRIMARY orders/PARAM.region
87
+ ```
88
+
89
+ ## BAA and Site Keys
90
+
91
+ The `/query` API endpoint requires BAA signing. Site keys inherit this requirement. For public-facing web apps, use published reports (`/report`) instead of `/query` for all read operations. Move filtering and aggregation logic into Chaprola programs using the QUERY and TABULATE language commands, publish them, and call `/report` from the frontend. Reserve site keys for write-only operations like `/insert-record`.