@coradb/coradb-sqlsyntaxchecker-wasm 0.3.3

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.
package/CHANGELOG.md ADDED
@@ -0,0 +1,55 @@
1
+ # Changelog
2
+
3
+ ## 0.3.3 (2026-11-04)
4
+
5
+ - Documentation only: the README, the changelog and a code comment no longer refer to other projects or to a local setup. The module, the code and the API are the same as in 0.3.2.
6
+
7
+ ## 0.3.2 (2026-11-04)
8
+
9
+ - **Renamed to `@coradb/coradb-sqlsyntaxchecker-wasm`** and published on npm as a public package of the `coradb` organization (`npm install @coradb/coradb-sqlsyntaxchecker-wasm`). Imports change accordingly; the module and API do not.
10
+ - **`checkSql()` and `validateSql()` use a fresh checker for each call**, made from the module compiled once, as the README recommends. `createSqlChecker()` also takes a compiled `WebAssembly.Module` (a new checker then costs about a millisecond).
11
+ - **Fixed: `describeError()` said "unexpected the end of the statement"**; it now says "Syntax error: unexpected end of the statement."
12
+ - **Fixed: a `[quoted name]` left open** was marked at the end of the text with nothing to mark; it is now marked from its bracket.
13
+ - Docs: README rewritten around using the package (it now includes what was in `Install-CoraDB-SQLSyntaxChecker-Wasm.md`, which is gone); `CLAUDE.md` added for working on the package.
14
+ - `build/generate.mjs`: a `//` comment inside a grammar action can no longer upset the brace count (the generated sources, and the module, are unchanged).
15
+ - **Fixed: names were not checked in longer texts, without a word.** The module kept at most 16,384 events (where the tables, columns and functions are); past that, `validate()` skipped every name check and returned `[]`. About 2,000 short statements, or an INSERT of about 16,000 rows, were enough. The buffer now grows with the text; if memory runs out, `validate()` throws instead of answering.
16
+ - **Fixed: a setting given as `undefined` or `null` was taken as `false`.** `{ noBackslashEscapes: undefined }` turned backslash escapes on. Such a setting now keeps the default.
17
+ - **Fixed: with backslash escapes on** (`noBackslashEscapes: false`), an escaped quote (`'it\'s'`) ended a string when counting an INSERT row's values or a call's arguments, which gave false "wrong number of values" reports; and an open string was marked from the wrong quote.
18
+ - **Fixed: a long token took time in proportion to the square of its length.** A 20 MB string literal did not finish in minutes; it now takes well under a second. (The lexer read the text 8 KB at a time and moved a long token back at every read; it now reads as much as its buffer holds.)
19
+ - **Fixed: a NUL character (U+0000) ended the text.** Whatever came before it was checked and the rest ignored, so `select 1` + NUL + anything was valid. It is now reported as an error at the NUL.
20
+ - **Fixed: a schema document with a synonym that leads back to itself** made `validate()` throw (stack overflow); columns, synonyms, serials or procedures without a `name` made it throw a TypeError. They are now let through or left out.
21
+ - `validate(sql, null)` no longer throws; `validate()` on long scripts is much faster (each statement's events are found once, not searched for).
22
+ - The module's WASI stand-ins: output, which the module never writes, is now reported as written in full (the C library retries a write that wrote nothing, which would have looped for ever), and a module that ends itself (`proc_exit`) has its instance replaced like one that traps. `builtin()` checks for memory too.
23
+ - `tools/coradbSchema.ts`: ENUM values and the element types of SET/MULTISET/LIST columns are read with the table's owner, so two owners' tables of the same name no longer get each other's.
24
+ - `build-info.json` now records the WASI SDK version (`wasiSdk`; 0.2.1's notes said it did, but it did not), and `source` is written the same way (`tesseractdb-develop\src\parser`) on every system. The build checks that `WASI_SDK_PATH` has a clang and that `CHECK_STACK_SIZE` is a sensible number.
25
+ - `schema-check.js`: `EV`, `nameParts` and `countItems`, which no one outside could reach, are no longer exported.
26
+ - Docs: README "Install" (releases, a clone); the synonyms exception to "missing means not known" in README and `index.d.ts`; comments in `check.c` and `generate.mjs`.
27
+ - The module was rebuilt from the same engine grammar and lexer (same SHA-256 as 0.3.1). Its exports are unchanged.
28
+
29
+ ## 0.3.1 (2026-10-30)
30
+
31
+ - **This package is now the single home of the checker.** Its build (`build/generate.mjs`, `check.c`, `shim.h`, `build.mjs`) is the maintained copy.
32
+ - No change to the module, its exports or the JavaScript API: `coradb-sqlsyntaxchecker.wasm` is the same as in 0.3.0.
33
+ - **License: MIT throughout.** `package.json`, `README.md`, `NOTICE` and the install guide said Apache-2.0; they now agree with `LICENSE` (MIT).
34
+
35
+ ## 0.3.0 (2026-10-30)
36
+
37
+ - **Fixed: a stray word after an expression was accepted**, e.g. `ORDER BY total DESCx` or `GROUP BY a foo`.
38
+ - The engine's grammar has a rule that matches an expression followed by a word only to refuse it: its action raises "invalid relational operator". The checker drops the engine's actions, so that refusal was lost.
39
+ - The build now finds rules whose action does nothing but raise a syntax error and makes the checker report it, at the stray word, with the engine's message. Today that is this one rule; any the engine adds are picked up the same way.
40
+ - `describeError` words it as `Syntax error: unexpected "DESCx" (invalid relational operator).`
41
+
42
+ ## 0.2.1 (2026-10-20)
43
+
44
+ - **Fixed: memory damage that carried over between checks.**
45
+ - The module's C stack was the WASI SDK's default 64 KB. The engine's GLR parser overran it on some graph queries and wrote over the module's static data and heap.
46
+ - Later checks on the same checker then crashed with "memory access out of bounds" or returned wrong results (an error with a garbage message for valid SQL).
47
+ - The fix: the stack is now 4 MB and placed first in memory (`-Wl,--stack-first`), so running past it traps immediately instead of overwriting data.
48
+ - **Fixed: "memory exhausted" on long statements the engine parses**, e.g. a MATCH of 40 patterns. The checker now uses the engine's YYMAXDEPTH (1000000) instead of bison's default (10000).
49
+ - **A statement nested too deeply now throws an Error.** This covers both the module's stack and the JavaScript engine's. The checker replaces its WebAssembly instance and goes on working.
50
+ - **Name checks:**
51
+ - names created by `CREATE VERTEX TABLE` / `CREATE EDGE TABLE` in the same script are known;
52
+ - `DROP … IF EXISTS` is not reported when the object is missing;
53
+ - variables of `list_comp(x IN …)` / `reduce(acc = …, x IN …)` are not taken for columns.
54
+ - **README:** creating a fresh checker for every check is now the recommended way, with the reasons why.
55
+ - **build-info.json** records the stack size and the WASI SDK version.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 ApsiswareDB
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/NOTICE ADDED
@@ -0,0 +1,14 @@
1
+ CoraDB-SQLSyntaxChecker-Wasm
2
+
3
+ This product includes software developed as part of the CoraDB engine:
4
+ Copyright 2008 Search Solution Corporation
5
+ Copyright 2016 CoraDB Corporation
6
+ CoraDB is a registered trademark of CoraDB Corporation.
7
+
8
+ coradb-sqlsyntaxchecker.wasm is compiled from the engine's SQL grammar (src/parser/
9
+ corasql_grammar.y) and lexer (src/parser/corasql_lexer.l), licensed under
10
+ the MIT License. They were modified for this package: the
11
+ grammar's C actions were replaced by hooks that only report where a
12
+ statement's names are, nothing being built from it; the built-in function
13
+ table and its argument checks were turned into a lookup table; and the lexer
14
+ was given a stand-in for the parser context it uses. See build/generate.mjs.
@@ -0,0 +1,362 @@
1
+ # The CoraDB schema document
2
+
3
+ A JSON document that describes what a CoraDB database defines: its tables,
4
+ views, node and edge classes, their columns, keys, indexes and comments, its
5
+ stored procedures and functions, serials and synonyms.
6
+
7
+ It is read from the database's catalog only. It never holds data from the
8
+ tables: no rows, no counts, no sample values.
9
+
10
+ It has two uses:
11
+
12
+ - **Checking SQL.** `validate(sql, { schema })` in this package looks up the
13
+ names a statement uses in it.
14
+ - **Describing the database** to a person or to an AI, for example as context
15
+ for writing queries. The comments and keys are there for that.
16
+
17
+ The rules of the format are also in machine-readable form: `schema.json` (JSON
18
+ Schema, draft 2020-12) to validate a document, and the `CoraDbSchema` types in
19
+ `index.d.ts` for TypeScript.
20
+
21
+ ## Contents
22
+
23
+ - [Rules that apply everywhere](#rules-that-apply-everywhere)
24
+ - [The document](#the-document)
25
+ - [Objects](#objects)
26
+ - [Columns](#columns)
27
+ - [Keys and indexes](#keys-and-indexes)
28
+ - [Procedures and functions](#procedures-and-functions)
29
+ - [Serials](#serials)
30
+ - [Synonyms](#synonyms)
31
+ - [A complete example](#a-complete-example)
32
+ - [Making a document](#making-a-document)
33
+ - [How the checker uses each part](#how-the-checker-uses-each-part)
34
+ - [Versions](#versions)
35
+
36
+ ## Rules that apply everywhere
37
+
38
+ - **Missing means "not known".** Every part except `format`, `version` and
39
+ `objects` is optional. A list or field that is left out means the producer
40
+ did not know it, never that there are none. An empty list (`[]`) means
41
+ "there are none". The checker never reports anything against a part that is
42
+ missing — with one exception: without `synonyms`, a table name is looked up
43
+ among the objects only.
44
+ - **Names are matched without regard to case**, as the engine does: `Orders`,
45
+ `orders` and `"ORDERS"` are the same object. Names are written as the
46
+ catalog spells them, without quotes.
47
+ - **Owners.** In CoraDB every object belongs to a user. Where an object or a
48
+ reference to one has an owner, it is given in `owner`, or as `owner.name` in
49
+ fields that point to another object (`under`, `references`, `target`). A
50
+ name without an owner matches that name under any owner.
51
+ - **Comments** are `null` when the catalog has none, and left out when the
52
+ producer could not read them.
53
+ - **Types** are written as CoraDB names them, in upper case: `INTEGER`,
54
+ `BIGINT`, `SMALLINT`, `NUMERIC`, `FLOAT`, `DOUBLE`, `VARCHAR`, `CHAR`,
55
+ `NCHAR`, `BIT`, `VARBIT`, `DATE`, `TIME`, `DATETIME`, `TIMESTAMP`, `ENUM`,
56
+ `SET`, `MULTISET`, `LIST`, `JSON`, `BLOB`, `CLOB`, `OBJECT`… A size is never
57
+ part of the type: it is in `length`, `precision` and `scale`.
58
+ - **Unknown fields** are allowed, so that a producer can add its own; readers
59
+ ignore what they do not know.
60
+
61
+ ## The document
62
+
63
+ ```json
64
+ {
65
+ "format": "coradb-schema",
66
+ "version": 1,
67
+ "database": "shop",
68
+ "objects": [],
69
+ "procedures": [],
70
+ "serials": [],
71
+ "synonyms": []
72
+ }
73
+ ```
74
+
75
+ | Field | Type | Required | Meaning |
76
+ |---|---|---|---|
77
+ | `format` | `"coradb-schema"` | yes | Says what the document is. |
78
+ | `version` | `1` | yes | The version of this format. |
79
+ | `database` | string | no | The database's name, for display. |
80
+ | `objects` | array of [objects](#objects) | yes | Tables, views, node classes and edge classes. |
81
+ | `procedures` | array of [procedures](#procedures-and-functions) | no | Stored procedures and functions. |
82
+ | `serials` | array of [serials](#serials) | no | Serials (sequences), not counting the ones behind AUTO_INCREMENT columns. |
83
+ | `synonyms` | array of [synonyms](#synonyms) | no | Synonyms. |
84
+
85
+ ## Objects
86
+
87
+ ```json
88
+ {
89
+ "name": "orders",
90
+ "owner": "dba",
91
+ "kind": "table",
92
+ "comment": "One row per customer order",
93
+ "under": [],
94
+ "columns": [ … ],
95
+ "primaryKey": ["id"],
96
+ "unique": [ … ],
97
+ "foreignKeys": [ … ],
98
+ "indexes": [ … ]
99
+ }
100
+ ```
101
+
102
+ | Field | Type | Required | Meaning |
103
+ |---|---|---|---|
104
+ | `name` | string | yes | The object's name. |
105
+ | `owner` | string | no | The user that owns it. |
106
+ | `kind` | `"table"`, `"view"`, `"node"`, `"edge"` | yes | What it is: a table, a view, a graph node class (vertex) or a graph edge class. |
107
+ | `comment` | string or null | no | The object's comment. |
108
+ | `under` | array of strings | no | The classes it inherits from (`CREATE … UNDER`), as `owner.name` or `name`. It has their columns as well as its own. |
109
+ | `columns` | array of [columns](#columns) | no | Its columns, in table order. Left out when they could not be read: then any column name is accepted. |
110
+ | `primaryKey` | array of strings | no | The columns of the primary key, in key order; `[]` when there is none. |
111
+ | `unique` | array | no | Unique keys: `{ "name": …, "columns": [ … ] }` each. |
112
+ | `foreignKeys` | array of [foreign keys](#keys-and-indexes) | no | Its foreign keys. |
113
+ | `indexes` | array of [indexes](#keys-and-indexes) | no | Its secondary indexes, the unique ones included; not the primary key or the foreign keys' own indexes. |
114
+ | `definition` | string | no | Views only: the SELECT the view is defined by. |
115
+
116
+ The partitions of a partitioned table are not listed as objects: they are the
117
+ table.
118
+
119
+ ## Columns
120
+
121
+ ```json
122
+ { "name": "total", "type": "NUMERIC", "precision": 12, "scale": 2,
123
+ "nullable": false, "default": "0", "comment": "Including VAT, in EUR" }
124
+ ```
125
+
126
+ | Field | Type | Required | Meaning |
127
+ |---|---|---|---|
128
+ | `name` | string | yes | The column's name. |
129
+ | `type` | string | yes | Its type, without sizes (see [Rules](#rules-that-apply-everywhere)). |
130
+ | `length` | integer | no | Character and bit types: the declared length. Left out when there is no limit (a VARCHAR declared without one). |
131
+ | `precision` | integer | no | NUMERIC: total digits. |
132
+ | `scale` | integer | no | NUMERIC: digits after the point. |
133
+ | `values` | array of strings | no | ENUM: the values it allows, in order. |
134
+ | `elementType` | string | no | SET, MULTISET and LIST: the type of their elements. |
135
+ | `nullable` | boolean | no | Whether it takes NULL. |
136
+ | `default` | string or null | no | The default as SQL text: `'RO'` (a string, quotes included), `0`, `SYS_DATETIME`. `null` when there is none. |
137
+ | `autoIncrement` | boolean | no | `true` for an AUTO_INCREMENT column. |
138
+ | `comment` | string or null | no | The column's comment. |
139
+
140
+ Examples of types:
141
+
142
+ | Declared as | Written as |
143
+ |---|---|
144
+ | `INT` | `{ "type": "INTEGER" }` |
145
+ | `VARCHAR(100)` | `{ "type": "VARCHAR", "length": 100 }` |
146
+ | `VARCHAR` / `STRING` | `{ "type": "VARCHAR" }` |
147
+ | `CHAR(2)` | `{ "type": "CHAR", "length": 2 }` |
148
+ | `NUMERIC(12,2)` | `{ "type": "NUMERIC", "precision": 12, "scale": 2 }` |
149
+ | `ENUM('new','paid')` | `{ "type": "ENUM", "values": ["new", "paid"] }` |
150
+ | `SET OF VARCHAR(20)` | `{ "type": "SET", "elementType": "VARCHAR" }` |
151
+
152
+ ## Keys and indexes
153
+
154
+ A foreign key:
155
+
156
+ ```json
157
+ { "name": "fk_orders_customer", "columns": ["customer_id"],
158
+ "references": "dba.customers", "refColumns": ["id"],
159
+ "onDelete": "CASCADE", "onUpdate": "RESTRICT" }
160
+ ```
161
+
162
+ | Field | Type | Required | Meaning |
163
+ |---|---|---|---|
164
+ | `name` | string | yes | The constraint's name. |
165
+ | `columns` | array of strings | yes | Its columns in this object, in order. |
166
+ | `references` | string | yes | The object it points to, as `owner.name`, or `name` when the owner is not known. Empty when the target could not be read. |
167
+ | `refColumns` | array of strings | yes | The columns it points to, in the same order as `columns`. |
168
+ | `onDelete`, `onUpdate` | string | no | `CASCADE`, `RESTRICT`, `NO ACTION` or `SET NULL`. Left out when not known. |
169
+
170
+ An index:
171
+
172
+ ```json
173
+ { "name": "idx_orders_status", "columns": ["status"], "unique": false, "comment": null }
174
+ ```
175
+
176
+ | Field | Type | Required | Meaning |
177
+ |---|---|---|---|
178
+ | `name` | string | yes | The index's name. |
179
+ | `columns` | array of strings | yes | Its columns, in key order. An index on an expression has none listed for that part. |
180
+ | `unique` | boolean | yes | Whether it is unique. |
181
+ | `comment` | string or null | no | The index's comment. |
182
+
183
+ ## Procedures and functions
184
+
185
+ ```json
186
+ { "name": "calc_tax", "owner": "dba", "kind": "function", "returns": "NUMERIC",
187
+ "comment": "VAT for an amount, by country",
188
+ "params": [
189
+ { "name": "amount", "type": "NUMERIC", "mode": "IN" },
190
+ { "name": "country", "type": "CHAR", "mode": "IN", "comment": "ISO 3166 code" }
191
+ ] }
192
+ ```
193
+
194
+ | Field | Type | Required | Meaning |
195
+ |---|---|---|---|
196
+ | `name` | string | yes | Its name. |
197
+ | `owner` | string | no | The user that owns it. |
198
+ | `kind` | `"procedure"`, `"function"` | yes | A function returns a value and is used in expressions; a procedure is run with `CALL`. |
199
+ | `returns` | string | no | Functions only: the type it returns. |
200
+ | `comment` | string or null | no | Its comment. |
201
+ | `params` | array | no | Its parameters, in order. Left out when not known. |
202
+
203
+ Each parameter:
204
+
205
+ | Field | Type | Required | Meaning |
206
+ |---|---|---|---|
207
+ | `name` | string | yes | The parameter's name. |
208
+ | `type` | string | yes | Its type. |
209
+ | `mode` | `"IN"`, `"OUT"`, `"INOUT"` | yes | Which way the value goes. |
210
+ | `comment` | string or null | no | Its comment. |
211
+
212
+ ## Serials
213
+
214
+ ```json
215
+ { "name": "order_no", "owner": "dba", "comment": "Printed order numbers" }
216
+ ```
217
+
218
+ | Field | Type | Required | Meaning |
219
+ |---|---|---|---|
220
+ | `name` | string | yes | Its name, as used in `order_no.NEXT_VALUE`. |
221
+ | `owner` | string | no | The user that owns it. |
222
+ | `comment` | string or null | no | Its comment. |
223
+
224
+ The serials the engine makes for AUTO_INCREMENT columns are not listed: the
225
+ columns are marked `"autoIncrement": true` instead.
226
+
227
+ ## Synonyms
228
+
229
+ ```json
230
+ { "name": "o", "owner": "dba", "target": "dba.orders", "comment": null }
231
+ ```
232
+
233
+ | Field | Type | Required | Meaning |
234
+ |---|---|---|---|
235
+ | `name` | string | yes | The synonym. |
236
+ | `owner` | string | no | The user that owns it. |
237
+ | `target` | string | yes | What it stands for, as `owner.name`. |
238
+ | `comment` | string or null | no | Its comment. |
239
+
240
+ ## A complete example
241
+
242
+ ```json
243
+ {
244
+ "format": "coradb-schema",
245
+ "version": 1,
246
+ "database": "shop",
247
+ "objects": [
248
+ {
249
+ "name": "customers", "owner": "dba", "kind": "table",
250
+ "comment": "People and companies that place orders",
251
+ "columns": [
252
+ { "name": "id", "type": "INTEGER", "nullable": false, "default": null, "autoIncrement": true, "comment": null },
253
+ { "name": "name", "type": "VARCHAR", "length": 100, "nullable": false, "default": null, "comment": "Full name" },
254
+ { "name": "country", "type": "CHAR", "length": 2, "nullable": true, "default": "'RO'", "comment": "ISO 3166 code" },
255
+ { "name": "kind", "type": "ENUM", "values": ["person", "company"], "nullable": false, "default": null, "comment": null }
256
+ ],
257
+ "primaryKey": ["id"],
258
+ "unique": [ { "name": "u_customers_name", "columns": ["name"] } ],
259
+ "foreignKeys": [],
260
+ "indexes": [ { "name": "u_customers_name", "columns": ["name"], "unique": true, "comment": null } ]
261
+ },
262
+ {
263
+ "name": "orders", "owner": "dba", "kind": "table",
264
+ "comment": "One row per customer order",
265
+ "columns": [
266
+ { "name": "id", "type": "INTEGER", "nullable": false, "default": null, "autoIncrement": true, "comment": null },
267
+ { "name": "customer_id", "type": "INTEGER", "nullable": false, "default": null, "comment": null },
268
+ { "name": "total", "type": "NUMERIC", "precision": 12, "scale": 2, "nullable": false, "default": "0", "comment": "Including VAT, in EUR" },
269
+ { "name": "created", "type": "DATETIME", "nullable": false, "default": "SYS_DATETIME", "comment": null }
270
+ ],
271
+ "primaryKey": ["id"],
272
+ "unique": [],
273
+ "foreignKeys": [
274
+ { "name": "fk_orders_customer", "columns": ["customer_id"], "references": "dba.customers", "refColumns": ["id"], "onDelete": "RESTRICT", "onUpdate": "RESTRICT" }
275
+ ],
276
+ "indexes": [ { "name": "idx_orders_created", "columns": ["created"], "unique": false, "comment": null } ]
277
+ },
278
+ {
279
+ "name": "big_orders", "owner": "dba", "kind": "view", "comment": "Orders over 1000 EUR",
280
+ "definition": "SELECT id, customer_id, total FROM orders WHERE total > 1000",
281
+ "columns": [
282
+ { "name": "id", "type": "INTEGER", "nullable": true, "default": null, "comment": null },
283
+ { "name": "customer_id", "type": "INTEGER", "nullable": true, "default": null, "comment": null },
284
+ { "name": "total", "type": "NUMERIC", "precision": 12, "scale": 2, "nullable": true, "default": null, "comment": null }
285
+ ]
286
+ },
287
+ {
288
+ "name": "viewer", "owner": "dba", "kind": "node", "comment": "A registered viewer",
289
+ "columns": [ { "name": "id", "type": "INTEGER", "nullable": false }, { "name": "name", "type": "VARCHAR", "length": 100 } ],
290
+ "primaryKey": ["id"]
291
+ },
292
+ {
293
+ "name": "rated", "owner": "dba", "kind": "edge", "comment": "A viewer rated a movie",
294
+ "columns": [ { "name": "stars", "type": "SMALLINT", "nullable": true } ]
295
+ }
296
+ ],
297
+ "procedures": [
298
+ { "name": "calc_tax", "owner": "dba", "kind": "function", "returns": "NUMERIC", "comment": "VAT for an amount, by country",
299
+ "params": [ { "name": "amount", "type": "NUMERIC", "mode": "IN", "comment": null },
300
+ { "name": "country", "type": "CHAR", "mode": "IN", "comment": null } ] },
301
+ { "name": "close_month", "owner": "dba", "kind": "procedure", "comment": null,
302
+ "params": [ { "name": "month", "type": "DATE", "mode": "IN", "comment": null } ] }
303
+ ],
304
+ "serials": [ { "name": "order_no", "owner": "dba", "comment": "Printed order numbers" } ],
305
+ "synonyms": [ { "name": "o", "owner": "dba", "target": "dba.orders", "comment": null } ]
306
+ }
307
+ ```
308
+
309
+ ## Making a document
310
+
311
+ `tools/coradbSchema.ts` reads it from a live database. It is one
312
+ self-contained file, meant to be copied into an app's server. It works with
313
+ any driver: give it a function that runs a query and returns the rows as
314
+ objects keyed by lower-cased column name.
315
+
316
+ ```ts
317
+ import { readCoraDbSchema } from './coradbSchema';
318
+
319
+ const schema = await readCoraDbSchema(async (sql) => {
320
+ const r = await conn.query(sql);
321
+ return r.rows.map((row) => Object.fromEntries(r.columns.map((c, i) => [c.toLowerCase(), row[i]])));
322
+ });
323
+ ```
324
+
325
+ It reads the catalog views — `db_class`, `db_attribute`,
326
+ `db_attr_setdomain_elm`, `db_direct_super_class`, `db_index`,
327
+ `db_index_key`, `db_partition`, `db_vclass`, `db_serial`,
328
+ `db_stored_procedure`, `db_stored_procedure_args`, `db_synonym` — plus
329
+ `SHOW CREATE TABLE` for tables with foreign keys (where they point is only in
330
+ the DDL) and `SHOW COLUMNS` for tables with ENUM columns (for their values).
331
+ Engine builds differ in which catalog columns they have, so each read tries
332
+ the fullest form first and falls back to plainer ones; a part that cannot be
333
+ read at all is left out of the document.
334
+
335
+ It lists what the connection's user can see. System classes (the engine's own
336
+ catalog) are left out.
337
+
338
+ A document can also be written by hand, or by another program: only `format`,
339
+ `version` and `objects` with each object's `name` and `kind` are required.
340
+
341
+ ## How the checker uses each part
342
+
343
+ | Part | What it lets `validate()` check | Without it |
344
+ |---|---|---|
345
+ | `objects` | That tables, views, node and edge classes exist; that a MATCH node label is a node class and an edge label an edge class. | — (required) |
346
+ | `columns` | That columns exist; ambiguous column names; INSERT column lists; how many values an INSERT row needs. | Any column of that object is accepted. |
347
+ | `under` | Inherited columns. | — |
348
+ | `procedures` | That called functions and procedures exist; how many arguments they take; a procedure used where a value is needed. | Function names that are not built-in are not checked. |
349
+ | `serials` | That `name.NEXT_VALUE` and `name.CURRENT_VALUE` name a serial. | Serials are not checked. |
350
+ | `synonyms` | That a synonym used as a table name stands for an existing object. | A name only a synonym would explain is reported as unknown. |
351
+ | comments, keys, indexes, `definition`, types | Nothing: they are there to describe the database. | — |
352
+
353
+ The built-in functions, the system catalog classes (`db_*`) and the
354
+ pseudo-columns (`ROWNUM`, `LEVEL`, …) belong to the engine, not to a database,
355
+ so they are not in the document: the checker knows them itself.
356
+
357
+ ## Versions
358
+
359
+ `version` is raised only when a change would make an older reader
360
+ misunderstand a document: a field changing its meaning, or a new required
361
+ field. Adding an optional field does not change the version; readers ignore
362
+ fields they do not know.