sumak 0.0.9 → 0.0.10
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/README.md +149 -2
- package/dist/builder/json-optics.d.mts +106 -0
- package/dist/builder/json-optics.mjs +110 -0
- package/dist/index.d.mts +7 -0
- package/dist/index.mjs +8 -0
- package/dist/normalize/expression.d.mts +26 -0
- package/dist/normalize/expression.mjs +330 -0
- package/dist/normalize/index.d.mts +4 -0
- package/dist/normalize/index.mjs +3 -0
- package/dist/normalize/query.d.mts +14 -0
- package/dist/normalize/query.mjs +126 -0
- package/dist/normalize/types.d.mts +38 -0
- package/dist/normalize/types.mjs +7 -0
- package/dist/optimize/index.d.mts +3 -0
- package/dist/optimize/index.mjs +2 -0
- package/dist/optimize/optimizer.d.mts +28 -0
- package/dist/optimize/optimizer.mjs +37 -0
- package/dist/optimize/rules.d.mts +31 -0
- package/dist/optimize/rules.mjs +161 -0
- package/dist/optimize/types.d.mts +29 -0
- package/dist/optimize/types.mjs +1 -0
- package/dist/sumak.d.mts +21 -3
- package/dist/sumak.mjs +26 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -41,6 +41,9 @@
|
|
|
41
41
|
- [Schema Builder (DDL)](#schema-builder-ddl)
|
|
42
42
|
- [Full-Text Search](#full-text-search)
|
|
43
43
|
- [Temporal Tables](#temporal-tables-sql2011)
|
|
44
|
+
- [JSON Optics](#json-optics)
|
|
45
|
+
- [Compiled Queries](#compiled-queries)
|
|
46
|
+
- [Query Optimization](#query-optimization)
|
|
44
47
|
- [Plugins](#plugins)
|
|
45
48
|
- [Hooks](#hooks)
|
|
46
49
|
- [Dialects](#dialects)
|
|
@@ -428,6 +431,8 @@ db.selectFrom("users")
|
|
|
428
431
|
.toSQL()
|
|
429
432
|
```
|
|
430
433
|
|
|
434
|
+
> For composable, type-tracked JSON navigation, see [JSON Optics](#json-optics).
|
|
435
|
+
|
|
431
436
|
### PostgreSQL Array Operators
|
|
432
437
|
|
|
433
438
|
```ts
|
|
@@ -1012,6 +1017,136 @@ Modes: `as_of`, `from_to`, `between`, `contained_in`, `all`.
|
|
|
1012
1017
|
|
|
1013
1018
|
---
|
|
1014
1019
|
|
|
1020
|
+
## JSON Optics
|
|
1021
|
+
|
|
1022
|
+
Composable, type-tracked JSON column navigation. Each `.at()` step tracks the type at that level.
|
|
1023
|
+
|
|
1024
|
+
```ts
|
|
1025
|
+
import { jsonCol } from "sumak"
|
|
1026
|
+
|
|
1027
|
+
// Navigate into JSON: -> (returns JSON)
|
|
1028
|
+
db.selectFrom("users")
|
|
1029
|
+
.selectExprs(jsonCol("data").at("address").at("city").asText().as("city"))
|
|
1030
|
+
.toSQL()
|
|
1031
|
+
// SELECT "data"->'address'->>'city' AS "city" FROM "users"
|
|
1032
|
+
|
|
1033
|
+
// Text extraction: ->> (returns text)
|
|
1034
|
+
db.selectFrom("users").selectExprs(jsonCol("meta").text("name").as("metaName")).toSQL()
|
|
1035
|
+
// SELECT "meta"->>'name' AS "metaName" FROM "users"
|
|
1036
|
+
|
|
1037
|
+
// PG path operators: #> and #>>
|
|
1038
|
+
jsonCol("data").atPath("address.city") // #> (returns JSON)
|
|
1039
|
+
jsonCol("data").textPath("address.city") // #>> (returns text)
|
|
1040
|
+
|
|
1041
|
+
// With table prefix
|
|
1042
|
+
jsonCol("data", "users").at("settings").asText()
|
|
1043
|
+
```
|
|
1044
|
+
|
|
1045
|
+
Type-safe with generics:
|
|
1046
|
+
|
|
1047
|
+
```ts
|
|
1048
|
+
interface UserProfile {
|
|
1049
|
+
address: { city: string; zip: string }
|
|
1050
|
+
preferences: { theme: string }
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
// Type narrows at each level
|
|
1054
|
+
jsonCol<UserProfile>("profile")
|
|
1055
|
+
.at("address") // JsonOptic<{ city: string; zip: string }>
|
|
1056
|
+
.at("city") // JsonOptic<string>
|
|
1057
|
+
.asText() // JsonExpr<string>
|
|
1058
|
+
```
|
|
1059
|
+
|
|
1060
|
+
---
|
|
1061
|
+
|
|
1062
|
+
## Compiled Queries
|
|
1063
|
+
|
|
1064
|
+
Pre-bake SQL at setup time. At runtime, only fill parameters — zero AST traversal.
|
|
1065
|
+
|
|
1066
|
+
```ts
|
|
1067
|
+
import { placeholder, compileQuery } from "sumak"
|
|
1068
|
+
|
|
1069
|
+
// Define query with named placeholders
|
|
1070
|
+
const findUser = compileQuery<{ userId: number }>(
|
|
1071
|
+
db
|
|
1072
|
+
.selectFrom("users")
|
|
1073
|
+
.select("id", "name")
|
|
1074
|
+
.where(({ id }) => id.eq(placeholder("userId")))
|
|
1075
|
+
.build(),
|
|
1076
|
+
db.printer(),
|
|
1077
|
+
)
|
|
1078
|
+
|
|
1079
|
+
// Runtime — same SQL string, different params:
|
|
1080
|
+
findUser({ userId: 42 })
|
|
1081
|
+
// → { sql: 'SELECT "id", "name" FROM "users" WHERE "id" = $1', params: [42] }
|
|
1082
|
+
|
|
1083
|
+
findUser({ userId: 99 })
|
|
1084
|
+
// → { sql: 'SELECT "id", "name" FROM "users" WHERE "id" = $1', params: [99] }
|
|
1085
|
+
|
|
1086
|
+
// Inspect the pre-baked SQL
|
|
1087
|
+
findUser.sql // 'SELECT "id", "name" FROM "users" WHERE "id" = $1'
|
|
1088
|
+
```
|
|
1089
|
+
|
|
1090
|
+
---
|
|
1091
|
+
|
|
1092
|
+
## Query Optimization
|
|
1093
|
+
|
|
1094
|
+
sumak automatically normalizes and optimizes queries through two new pipeline layers.
|
|
1095
|
+
|
|
1096
|
+
### Normalization (NbE)
|
|
1097
|
+
|
|
1098
|
+
Enabled by default. Reduces expressions to canonical form:
|
|
1099
|
+
|
|
1100
|
+
- **Flatten AND/OR:** `(a AND (b AND c))` → `(a AND b AND c)`
|
|
1101
|
+
- **Deduplicate:** `a = 1 AND b = 2 AND a = 1` → `a = 1 AND b = 2`
|
|
1102
|
+
- **Simplify tautologies:** `x AND true` → `x`, `x OR false` → `x`
|
|
1103
|
+
- **Constant folding:** `1 + 2` → `3`
|
|
1104
|
+
- **Double negation:** `NOT NOT x` → `x`
|
|
1105
|
+
- **Comparison normalization:** `1 = x` → `x = 1`
|
|
1106
|
+
|
|
1107
|
+
### Optimization (Rewrite Rules)
|
|
1108
|
+
|
|
1109
|
+
Built-in rules applied after normalization:
|
|
1110
|
+
|
|
1111
|
+
- **Predicate pushdown:** Moves WHERE conditions into JOIN ON when they reference a single table
|
|
1112
|
+
- **Subquery flattening:** `SELECT * FROM (SELECT * FROM t)` → `SELECT * FROM t`
|
|
1113
|
+
- **WHERE true removal:** Cleans up `WHERE true` left by plugins
|
|
1114
|
+
|
|
1115
|
+
### Configuration
|
|
1116
|
+
|
|
1117
|
+
```ts
|
|
1118
|
+
// Default: both enabled
|
|
1119
|
+
const db = sumak({ dialect: pgDialect(), tables: { ... } })
|
|
1120
|
+
|
|
1121
|
+
// Disable normalization
|
|
1122
|
+
const db = sumak({ dialect: pgDialect(), normalize: false, tables: { ... } })
|
|
1123
|
+
|
|
1124
|
+
// Disable optimization
|
|
1125
|
+
const db = sumak({ dialect: pgDialect(), optimizeQueries: false, tables: { ... } })
|
|
1126
|
+
```
|
|
1127
|
+
|
|
1128
|
+
### Custom Rewrite Rules
|
|
1129
|
+
|
|
1130
|
+
```ts
|
|
1131
|
+
import { createRule } from "sumak"
|
|
1132
|
+
|
|
1133
|
+
const defaultLimit = createRule({
|
|
1134
|
+
name: "default-limit",
|
|
1135
|
+
match: (node) => node.type === "select" && !node.limit,
|
|
1136
|
+
apply: (node) => ({ ...node, limit: { type: "literal", value: 1000 } }),
|
|
1137
|
+
})
|
|
1138
|
+
|
|
1139
|
+
const db = sumak({
|
|
1140
|
+
dialect: pgDialect(),
|
|
1141
|
+
rules: [defaultLimit],
|
|
1142
|
+
tables: { ... },
|
|
1143
|
+
})
|
|
1144
|
+
```
|
|
1145
|
+
|
|
1146
|
+
Rules are applied bottom-up until a fixpoint (no more changes). Max 10 iterations by default.
|
|
1147
|
+
|
|
1148
|
+
---
|
|
1149
|
+
|
|
1015
1150
|
## Plugins
|
|
1016
1151
|
|
|
1017
1152
|
### WithSchemaPlugin
|
|
@@ -1217,7 +1352,7 @@ import { serial, text } from "sumak/schema"
|
|
|
1217
1352
|
|
|
1218
1353
|
## Architecture
|
|
1219
1354
|
|
|
1220
|
-
sumak uses a
|
|
1355
|
+
sumak uses a 7-layer pipeline. Your code never touches SQL strings — everything flows through an AST.
|
|
1221
1356
|
|
|
1222
1357
|
```
|
|
1223
1358
|
┌─────────────────────────────────────────────────────────────────┐
|
|
@@ -1237,7 +1372,15 @@ sumak uses a 5-layer pipeline. Your code never touches SQL strings — everythin
|
|
|
1237
1372
|
│ Plugin.transformNode() → Hook "query:before" │
|
|
1238
1373
|
│ → AST rewriting, tenant isolation, soft delete, logging │
|
|
1239
1374
|
├─────────────────────────────────────────────────────────────────┤
|
|
1240
|
-
│ 5.
|
|
1375
|
+
│ 5. NORMALIZE (NbE) │
|
|
1376
|
+
│ Predicate simplification, constant folding, deduplication │
|
|
1377
|
+
│ → Canonical form via Normalization by Evaluation │
|
|
1378
|
+
├─────────────────────────────────────────────────────────────────┤
|
|
1379
|
+
│ 6. OPTIMIZE (Rewrite Rules) │
|
|
1380
|
+
│ Predicate pushdown, subquery flattening, user rules │
|
|
1381
|
+
│ → Declarative rules applied to fixpoint │
|
|
1382
|
+
├─────────────────────────────────────────────────────────────────┤
|
|
1383
|
+
│ 7. PRINTER │
|
|
1241
1384
|
│ .toSQL() → { sql: "SELECT ...", params: [...] } │
|
|
1242
1385
|
│ → Dialect-specific: PG ($1), MySQL (?), MSSQL (@p0) │
|
|
1243
1386
|
└─────────────────────────────────────────────────────────────────┘
|
|
@@ -1249,6 +1392,8 @@ The query is never a string until the very last step. This means:
|
|
|
1249
1392
|
|
|
1250
1393
|
- **Plugins can rewrite queries** — add WHERE clauses, prefix schemas, transform joins
|
|
1251
1394
|
- **Hooks can inspect/modify** — logging, tracing, tenant isolation
|
|
1395
|
+
- **Normalize simplifies** — duplicate predicates, tautologies, constant expressions
|
|
1396
|
+
- **Optimize rewrites** — predicate pushdown, subquery flattening, custom rules
|
|
1252
1397
|
- **Printers are swappable** — same AST, different SQL per dialect
|
|
1253
1398
|
- **No SQL injection** — values are always parameterized
|
|
1254
1399
|
|
|
@@ -1258,6 +1403,8 @@ The query is never a string until the very last step. This means:
|
|
|
1258
1403
|
- **Immutable builders** — every method returns a new instance
|
|
1259
1404
|
- **Proxy-based column access** — `({ age }) => age.gt(18)` with full type safety
|
|
1260
1405
|
- **Phantom types** — `Expression<T>` carries type info with zero runtime cost
|
|
1406
|
+
- **NbE normalization** — expressions reduced to canonical form before printing
|
|
1407
|
+
- **Compiled queries** — pre-bake SQL at setup, zero AST walk at runtime
|
|
1261
1408
|
|
|
1262
1409
|
---
|
|
1263
1410
|
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import type { ExpressionNode } from "../ast/nodes.mjs";
|
|
2
|
+
import type { Expression } from "../ast/typed-expression.mjs";
|
|
3
|
+
/**
|
|
4
|
+
* JSON optics — composable, type-tracked JSON column navigation.
|
|
5
|
+
*
|
|
6
|
+
* Each `.at()` step creates a new optic that tracks the type at that level.
|
|
7
|
+
* The final expression is a chain of JSON access operators.
|
|
8
|
+
*
|
|
9
|
+
* ```ts
|
|
10
|
+
* // Type-tracked navigation:
|
|
11
|
+
* jsonCol<UserProfile>("profile")
|
|
12
|
+
* .at("address") // JsonOptic<Address>
|
|
13
|
+
* .at("city") // JsonOptic<string>
|
|
14
|
+
* .asText() // Expression<string>
|
|
15
|
+
*
|
|
16
|
+
* // Use in SELECT:
|
|
17
|
+
* db.selectFrom("users")
|
|
18
|
+
* .selectExprs(jsonCol("data").at("name").asText().as("name"))
|
|
19
|
+
* ```
|
|
20
|
+
*
|
|
21
|
+
* **Dialect-aware operators:**
|
|
22
|
+
* - `.at("key")` → `->` (returns JSON)
|
|
23
|
+
* - `.asText()` → `->>` (returns text)
|
|
24
|
+
* - `.atPath("a.b.c")` → `#>` (PG path operator)
|
|
25
|
+
* - `.asTextPath("a.b.c")` → `#>>` (PG text path operator)
|
|
26
|
+
*/
|
|
27
|
+
export declare class JsonOptic<T = unknown> {
|
|
28
|
+
readonly _type: T;
|
|
29
|
+
constructor(node: ExpressionNode);
|
|
30
|
+
/**
|
|
31
|
+
* Navigate into a JSON object key. Returns JSON type.
|
|
32
|
+
*
|
|
33
|
+
* ```ts
|
|
34
|
+
* jsonCol("data").at("address") // → data->'address'
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
at<K extends string>(key: K): JsonOptic<T extends Record<K, infer V> ? V : unknown>;
|
|
38
|
+
/**
|
|
39
|
+
* Navigate into a JSON object key and extract as text.
|
|
40
|
+
*
|
|
41
|
+
* ```ts
|
|
42
|
+
* jsonCol("data").text("name") // → data->>'name' (returns string)
|
|
43
|
+
* ```
|
|
44
|
+
*/
|
|
45
|
+
text<K extends string>(key: K): JsonExpr<string>;
|
|
46
|
+
/**
|
|
47
|
+
* Navigate by PG JSON path operator `#>`.
|
|
48
|
+
*
|
|
49
|
+
* ```ts
|
|
50
|
+
* jsonCol("data").atPath("address.city") // → data#>'{address,city}'
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
53
|
+
atPath(path: string): JsonOptic<unknown>;
|
|
54
|
+
/**
|
|
55
|
+
* Navigate by PG JSON text path operator `#>>`.
|
|
56
|
+
*
|
|
57
|
+
* ```ts
|
|
58
|
+
* jsonCol("data").textPath("address.city") // → data#>>'{address,city}'
|
|
59
|
+
* ```
|
|
60
|
+
*/
|
|
61
|
+
textPath(path: string): JsonExpr<string>;
|
|
62
|
+
/**
|
|
63
|
+
* Cast current JSON value to text (`->>`).
|
|
64
|
+
*/
|
|
65
|
+
asText(): JsonExpr<string>;
|
|
66
|
+
/**
|
|
67
|
+
* Get the underlying expression node.
|
|
68
|
+
*/
|
|
69
|
+
toExpression(): Expression<T>;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* A JSON expression that can be aliased and used in SELECT/WHERE.
|
|
73
|
+
* This is the "leaf" of the optics chain.
|
|
74
|
+
*/
|
|
75
|
+
export declare class JsonExpr<T> {
|
|
76
|
+
readonly _type: T;
|
|
77
|
+
constructor(node: ExpressionNode);
|
|
78
|
+
/**
|
|
79
|
+
* Alias this expression for use in SELECT.
|
|
80
|
+
*
|
|
81
|
+
* ```ts
|
|
82
|
+
* jsonCol("data").text("name").as("userName")
|
|
83
|
+
* ```
|
|
84
|
+
*/
|
|
85
|
+
as(alias: string): Expression<T>;
|
|
86
|
+
/**
|
|
87
|
+
* Get the underlying expression node.
|
|
88
|
+
*/
|
|
89
|
+
toExpression(): Expression<T>;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Create a JSON optic from a column name.
|
|
93
|
+
*
|
|
94
|
+
* ```ts
|
|
95
|
+
* const profileCity = jsonCol<UserProfile>("profile").at("address").at("city").asText()
|
|
96
|
+
* ```
|
|
97
|
+
*/
|
|
98
|
+
export declare function jsonCol<T = unknown>(column: string, table?: string): JsonOptic<T>;
|
|
99
|
+
/**
|
|
100
|
+
* Create a JSON optic from an existing expression.
|
|
101
|
+
*
|
|
102
|
+
* ```ts
|
|
103
|
+
* const optic = jsonExpr<Config>(someExpression).at("settings")
|
|
104
|
+
* ```
|
|
105
|
+
*/
|
|
106
|
+
export declare function jsonExpr<T = unknown>(expr: Expression<any>): JsonOptic<T>;
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
|
|
2
|
+
export class JsonOptic {
|
|
3
|
+
|
|
4
|
+
_node;
|
|
5
|
+
constructor(node) {
|
|
6
|
+
this._node = node;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
at(key) {
|
|
10
|
+
const node = {
|
|
11
|
+
type: "json_access",
|
|
12
|
+
expr: this._node,
|
|
13
|
+
path: key,
|
|
14
|
+
operator: "->"
|
|
15
|
+
};
|
|
16
|
+
return new JsonOptic(node);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
text(key) {
|
|
20
|
+
const node = {
|
|
21
|
+
type: "json_access",
|
|
22
|
+
expr: this._node,
|
|
23
|
+
path: key,
|
|
24
|
+
operator: "->>"
|
|
25
|
+
};
|
|
26
|
+
return new JsonExpr(node);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
atPath(path) {
|
|
30
|
+
const node = {
|
|
31
|
+
type: "json_access",
|
|
32
|
+
expr: this._node,
|
|
33
|
+
path,
|
|
34
|
+
operator: "#>"
|
|
35
|
+
};
|
|
36
|
+
return new JsonOptic(node);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
textPath(path) {
|
|
40
|
+
const node = {
|
|
41
|
+
type: "json_access",
|
|
42
|
+
expr: this._node,
|
|
43
|
+
path,
|
|
44
|
+
operator: "#>>"
|
|
45
|
+
};
|
|
46
|
+
return new JsonExpr(node);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
asText() {
|
|
50
|
+
|
|
51
|
+
if (this._node.type === "json_access") {
|
|
52
|
+
const ja = this._node;
|
|
53
|
+
if (ja.operator === "->") {
|
|
54
|
+
return new JsonExpr({
|
|
55
|
+
...ja,
|
|
56
|
+
operator: "->>"
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return new JsonExpr({
|
|
62
|
+
type: "cast",
|
|
63
|
+
expr: this._node,
|
|
64
|
+
dataType: "text"
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
toExpression() {
|
|
69
|
+
return this._node;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export class JsonExpr {
|
|
74
|
+
|
|
75
|
+
_node;
|
|
76
|
+
constructor(node) {
|
|
77
|
+
this._node = node;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
as(alias) {
|
|
81
|
+
if (this._node.type === "json_access") {
|
|
82
|
+
return {
|
|
83
|
+
...this._node,
|
|
84
|
+
alias
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
return {
|
|
88
|
+
type: "aliased_expr",
|
|
89
|
+
expr: this._node,
|
|
90
|
+
alias
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
toExpression() {
|
|
95
|
+
return this._node;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function jsonCol(column, table) {
|
|
100
|
+
const node = {
|
|
101
|
+
type: "column_ref",
|
|
102
|
+
column,
|
|
103
|
+
table
|
|
104
|
+
};
|
|
105
|
+
return new JsonOptic(node);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function jsonExpr(expr) {
|
|
109
|
+
return new JsonOptic(expr._node ?? expr);
|
|
110
|
+
}
|
package/dist/index.d.mts
CHANGED
|
@@ -60,4 +60,11 @@ export { DropTableBuilder, DropIndexBuilder, DropViewBuilder, TruncateTableBuild
|
|
|
60
60
|
export { DDLPrinter } from "./printer/ddl.mjs";
|
|
61
61
|
export { SchemaBuilder } from "./sumak.mjs";
|
|
62
62
|
export type { AlterColumnSet, AlterTableAction, AlterTableNode, CheckConstraintNode, ColumnDefinitionNode, CreateIndexNode, CreateTableNode, CreateViewNode, DDLNode, DropIndexNode, DropTableNode, DropViewNode, ForeignKeyAction, ForeignKeyConstraintNode, PrimaryKeyConstraintNode, TableConstraintNode, TruncateTableNode, UniqueConstraintNode } from "./ast/ddl-nodes.mjs";
|
|
63
|
+
export { normalizeExpression, normalizeQuery, toCNF, fromCNF } from "./normalize/index.mjs";
|
|
64
|
+
export type { CNF, NormalizeOptions } from "./normalize/index.mjs";
|
|
65
|
+
export { optimize, createRule, predicatePushdown, subqueryFlattening, removeWhereTrue, BUILTIN_RULES } from "./optimize/index.mjs";
|
|
66
|
+
export type { RewriteRule, OptimizeOptions } from "./optimize/index.mjs";
|
|
67
|
+
export { placeholder, compileQuery, collectPlaceholders, isPlaceholder } from "./builder/compiled.mjs";
|
|
68
|
+
export type { CompiledQueryFn, PlaceholderMarker } from "./builder/compiled.mjs";
|
|
69
|
+
export { JsonOptic, JsonExpr, jsonCol, jsonExpr } from "./builder/json-optics.mjs";
|
|
63
70
|
export { EmptyQueryError, InvalidExpressionError, SumakError, UnsupportedDialectFeatureError } from "./errors.mjs";
|
package/dist/index.mjs
CHANGED
|
@@ -58,4 +58,12 @@ export { DropTableBuilder, DropIndexBuilder, DropViewBuilder, TruncateTableBuild
|
|
|
58
58
|
export { DDLPrinter } from "./printer/ddl.mjs";
|
|
59
59
|
export { SchemaBuilder } from "./sumak.mjs";
|
|
60
60
|
|
|
61
|
+
export { normalizeExpression, normalizeQuery, toCNF, fromCNF } from "./normalize/index.mjs";
|
|
62
|
+
|
|
63
|
+
export { optimize, createRule, predicatePushdown, subqueryFlattening, removeWhereTrue, BUILTIN_RULES } from "./optimize/index.mjs";
|
|
64
|
+
|
|
65
|
+
export { placeholder, compileQuery, collectPlaceholders, isPlaceholder } from "./builder/compiled.mjs";
|
|
66
|
+
|
|
67
|
+
export { JsonOptic, JsonExpr, jsonCol, jsonExpr } from "./builder/json-optics.mjs";
|
|
68
|
+
|
|
61
69
|
export { EmptyQueryError, InvalidExpressionError, SumakError, UnsupportedDialectFeatureError } from "./errors.mjs";
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { ExpressionNode } from "../ast/nodes.mjs";
|
|
2
|
+
import type { CNF, NormalizeOptions } from "./types.mjs";
|
|
3
|
+
/**
|
|
4
|
+
* Normalize an expression node using NbE (Normalization by Evaluation).
|
|
5
|
+
*
|
|
6
|
+
* Pipeline: Expression → evaluate (semantic domain) → reify (canonical AST)
|
|
7
|
+
*
|
|
8
|
+
* Transformations:
|
|
9
|
+
* - Flatten nested AND/OR
|
|
10
|
+
* - Remove duplicate predicates
|
|
11
|
+
* - Simplify tautologies: `x AND true → x`, `x OR false → x`
|
|
12
|
+
* - Simplify contradictions: `x AND false → false`, `x OR true → true`
|
|
13
|
+
* - Fold constants: `1 + 2 → 3`
|
|
14
|
+
* - Simplify negation: `NOT NOT x → x`, `NOT true → false`
|
|
15
|
+
* - Normalize comparison direction: `1 = x → x = 1` (literal always on right)
|
|
16
|
+
*/
|
|
17
|
+
export declare function normalizeExpression(expr: ExpressionNode, opts?: NormalizeOptions): ExpressionNode;
|
|
18
|
+
/**
|
|
19
|
+
* Convert a WHERE expression to Conjunctive Normal Form.
|
|
20
|
+
* Top-level AND, inner OR.
|
|
21
|
+
*/
|
|
22
|
+
export declare function toCNF(expr: ExpressionNode): CNF;
|
|
23
|
+
/**
|
|
24
|
+
* Reify a CNF back to an ExpressionNode.
|
|
25
|
+
*/
|
|
26
|
+
export declare function fromCNF(cnf: CNF): ExpressionNode | undefined;
|