sqyrl 0.0.2 → 0.1.2

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 CHANGED
@@ -15,15 +15,44 @@ npm install sqyrl
15
15
  ```ts
16
16
  import { sqyrl } from "sqyrl";
17
17
 
18
- const sql = sqyrl(`SELECT id, name FROM public.users WHERE status = 'active' LIMIT 10`, {
19
- schema: "public",
18
+ const sql = sqyrl(`SELECT id, name FROM users WHERE status = 'active' LIMIT 10`, {
20
19
  table: "users",
21
- column: "tenant_id",
20
+ col: "tenant_id",
22
21
  value: "acme",
23
22
  });
24
23
 
25
24
  console.log(sql);
26
- // SELECT id, name FROM public.users WHERE public.users.tenant_id = 'acme' AND status = 'active' LIMIT 10
25
+ // SELECT id, name
26
+ // FROM users
27
+ // WHERE (users.tenant_id = 'acme'
28
+ // AND status = 'active') LIMIT 10
29
+ ```
30
+
31
+ Or, more usefully:
32
+
33
+ ```ts
34
+ import { makeSqyrl } from "sqyrl";
35
+ import { tool } from "ai";
36
+ import { sql } from "drizzle-orm";
37
+ import { db } from "@/db";
38
+
39
+ function makeSqlTool(orgId: string) {
40
+ // Create a sanitiser function for this tenant
41
+ const sqyrl = makeSqyrl({ table: "org", col: "id", value: orgId });
42
+
43
+ return tool({
44
+ description: "Run raw SQL against the DB",
45
+ inputSchema: z.object({ query: z.string() }),
46
+ execute: async ({ query }) => {
47
+ // The LLM can pass any query it likes, we'll sanitise it if possible
48
+ // and return helpful error messages if not
49
+ const sanitised = sqyrl(query);
50
+ // Now we can throw that straight at the db and be confident it'll only
51
+ // return data from the specified tenant
52
+ return db.execute(sql.raw(sanitised));
53
+ },
54
+ });
55
+ }
27
56
  ```
28
57
 
29
58
  `sqyrl` parses the SQL, enforces a mandatory equality filter on the given column as the outermost `AND` condition (so it cannot be short-circuited by agent-supplied `OR` clauses), and returns the sanitised SQL string.
@@ -35,12 +64,24 @@ console.log(sql);
35
64
 
36
65
  ## Development
37
66
 
67
+ First install [Vite+](https://viteplus.dev/guide/):
68
+
69
+ ```bash
70
+ curl -fsSL https://vite.plus | bash
71
+ ```
72
+
38
73
  Install dependencies:
39
74
 
40
75
  ```bash
41
76
  vp install
42
77
  ```
43
78
 
79
+ Format, lint, typecheck:
80
+
81
+ ```bash
82
+ vp check --fix
83
+ ```
84
+
44
85
  Run the unit tests:
45
86
 
46
87
  ```bash
package/dist/index.d.mts CHANGED
@@ -1,44 +1,217 @@
1
1
  //#region src/ast.d.ts
2
2
  interface SelectStatement {
3
- type: "select";
3
+ readonly type: "select";
4
+ distinct: Distinct | null;
4
5
  columns: Column[];
5
- from: TableRef;
6
- where: WhereExpr | null;
7
- limit: number | null;
6
+ from: SelectFrom;
7
+ joins: JoinClause[];
8
+ where: WhereRoot | null;
9
+ groupBy: GroupByClause | null;
10
+ having: HavingClause | null;
11
+ orderBy: OrderByClause | null;
12
+ limit: LimitClause | null;
13
+ offset: OffsetClause | null;
8
14
  }
9
- type WhereExpr = WhereAnd | WhereOr | WhereComparison;
15
+ interface Distinct {
16
+ readonly type: "distinct";
17
+ }
18
+ interface GroupByClause {
19
+ readonly type: "group_by";
20
+ items: WhereValue[];
21
+ }
22
+ interface HavingClause {
23
+ readonly type: "having";
24
+ expr: WhereExpr;
25
+ }
26
+ interface OrderByClause {
27
+ readonly type: "order_by";
28
+ items: OrderByItem[];
29
+ }
30
+ type SortDirection = "asc" | "desc";
31
+ type NullsOrder = "nulls_first" | "nulls_last";
32
+ interface OrderByItem {
33
+ readonly type: "order_by_item";
34
+ expr: WhereValue;
35
+ direction?: SortDirection;
36
+ nulls?: NullsOrder;
37
+ }
38
+ interface OffsetClause {
39
+ readonly type: "offset";
40
+ value: number;
41
+ }
42
+ type JoinType = "inner" | "inner_outer" | "left" | "left_outer" | "right" | "right_outer" | "full" | "full_outer" | "cross" | "natural";
43
+ type JoinCondition = {
44
+ readonly type: "join_on";
45
+ expr: WhereExpr;
46
+ } | {
47
+ readonly type: "join_using";
48
+ columns: string[];
49
+ };
50
+ interface JoinClause {
51
+ readonly type: "join";
52
+ joinType: JoinType;
53
+ table: TableRef;
54
+ condition: JoinCondition | null;
55
+ }
56
+ type SelectFrom = {
57
+ readonly type: "select_from";
58
+ table: TableRef;
59
+ };
60
+ type LimitClause = {
61
+ readonly type: "limit";
62
+ value: number;
63
+ };
64
+ type WhereRoot = {
65
+ readonly type: "where_root";
66
+ inner: WhereExpr;
67
+ };
68
+ type WhereExpr = WhereAnd | WhereOr | WhereNot | WhereComparison | WhereIsNull | WhereIsBool | WhereBetween | WhereIn | WhereLike;
10
69
  interface WhereAnd {
11
- type: "and";
70
+ readonly type: "where_and";
12
71
  left: WhereExpr;
13
72
  right: WhereExpr;
14
73
  }
15
74
  interface WhereOr {
16
- type: "or";
75
+ readonly type: "where_or";
17
76
  left: WhereExpr;
18
77
  right: WhereExpr;
19
78
  }
20
- interface WhereComparison {
21
- type: "comparison";
22
- operator: "=";
23
- column: ColumnRefNode;
79
+ type ComparisonOperator = "=" | "<>" | "!=" | "<" | ">" | "<=" | ">=";
80
+ interface WhereNot {
81
+ readonly type: "where_not";
82
+ expr: WhereExpr;
83
+ }
84
+ interface WhereIsNull {
85
+ readonly type: "where_is_null";
86
+ not: boolean;
87
+ expr: WhereValue;
88
+ }
89
+ type IsBoolTarget = boolean | "unknown";
90
+ interface WhereIsBool {
91
+ readonly type: "where_is_bool";
92
+ not: boolean;
93
+ expr: WhereValue;
94
+ target: IsBoolTarget;
95
+ }
96
+ interface WhereBetween {
97
+ readonly type: "where_between";
98
+ not: boolean;
99
+ expr: WhereValue;
100
+ low: WhereValue;
101
+ high: WhereValue;
102
+ }
103
+ interface WhereIn {
104
+ readonly type: "where_in";
105
+ not: boolean;
106
+ expr: WhereValue;
107
+ list: WhereValue[];
108
+ }
109
+ type LikeOp = "like";
110
+ interface WhereLike {
111
+ readonly type: "where_like";
112
+ not: boolean;
113
+ op: LikeOp;
114
+ expr: WhereValue;
115
+ pattern: WhereValue;
116
+ }
117
+ type ArithOp = "+" | "-" | "*" | "/" | "%" | "||";
118
+ interface WhereArith {
119
+ readonly type: "where_arith";
120
+ op: ArithOp;
121
+ left: WhereValue;
122
+ right: WhereValue;
123
+ }
124
+ interface WhereUnaryMinus {
125
+ readonly type: "where_unary_minus";
126
+ expr: WhereValue;
127
+ }
128
+ interface CaseWhen {
129
+ condition: WhereValue;
130
+ result: WhereValue;
131
+ }
132
+ interface CaseExpr {
133
+ readonly type: "case_expr";
134
+ subject: WhereValue | null;
135
+ whens: CaseWhen[];
136
+ else: WhereValue | null;
137
+ }
138
+ interface CastExpr {
139
+ readonly type: "cast_expr";
140
+ expr: WhereValue;
141
+ typeName: string;
142
+ }
143
+ type WhereValue = {
144
+ readonly type: "where_value";
145
+ kind: "string";
24
146
  value: string;
147
+ } | {
148
+ readonly type: "where_value";
149
+ kind: "integer";
150
+ value: number;
151
+ } | {
152
+ readonly type: "where_value";
153
+ kind: "float";
154
+ value: number;
155
+ } | {
156
+ readonly type: "where_value";
157
+ kind: "bool";
158
+ value: boolean;
159
+ } | {
160
+ readonly type: "where_value";
161
+ kind: "null";
162
+ } | {
163
+ readonly type: "where_value";
164
+ kind: "column_ref";
165
+ ref: ColumnRef;
166
+ } | {
167
+ readonly type: "where_value";
168
+ kind: "func_call";
169
+ func: FuncCall;
170
+ } | WhereArith | WhereUnaryMinus | CaseExpr | CastExpr;
171
+ interface WhereComparison {
172
+ readonly type: "where_comparison";
173
+ operator: ComparisonOperator;
174
+ left: WhereValue;
175
+ right: WhereValue;
25
176
  }
26
- interface ColumnRefNode {
27
- type: "column_ref";
177
+ interface ColumnRef {
178
+ readonly type: "column_ref";
179
+ schema?: string;
28
180
  table?: string;
29
181
  name: string;
30
182
  }
31
- interface Column {
32
- type: "wildcard" | "qualified_wildcard" | "qualified" | "simple";
183
+ type FuncCallArg = {
184
+ kind: "wildcard";
185
+ } | {
186
+ kind: "args";
187
+ distinct: boolean;
188
+ args: WhereValue[];
189
+ };
190
+ interface FuncCall {
191
+ readonly type: "func_call";
192
+ name: string;
193
+ args: FuncCallArg;
194
+ }
195
+ interface ColumnExpr {
196
+ readonly type: "column_expr";
197
+ kind: "wildcard" | "qualified_wildcard" | "expr";
33
198
  table?: string;
34
- name?: string;
35
- alias?: string;
199
+ expr?: WhereValue;
200
+ }
201
+ interface Column {
202
+ readonly type: "column";
203
+ expr: ColumnExpr;
204
+ alias?: Alias;
205
+ }
206
+ interface Alias {
207
+ readonly type: "alias";
208
+ name: string;
36
209
  }
37
210
  interface TableRef {
38
- type: "table";
211
+ readonly type: "table_ref";
39
212
  schema?: string;
40
213
  name: string;
41
- alias?: string;
214
+ alias?: Alias;
42
215
  }
43
216
  //#endregion
44
217
  //#region src/output.d.ts
@@ -48,31 +221,21 @@ declare function outputSql(ast: SelectStatement): string;
48
221
  declare function parseSql(expr: string): SelectStatement;
49
222
  //#endregion
50
223
  //#region src/sanitise.d.ts
51
- declare function sanitiseSql({
52
- ast,
224
+ interface WhereGuard {
225
+ schema?: string;
226
+ table: string;
227
+ col: string;
228
+ value: string | number;
229
+ }
230
+ declare function sanitiseSql(ast: SelectStatement, {
53
231
  schema,
54
232
  table,
55
233
  col,
56
234
  value
57
- }: {
58
- ast: SelectStatement;
59
- schema: string;
60
- table: string;
61
- col: string;
62
- value: string;
63
- }): SelectStatement;
235
+ }: WhereGuard): SelectStatement;
64
236
  //#endregion
65
237
  //#region src/index.d.ts
66
- declare function sqyrl(expr: string, {
67
- schema,
68
- table,
69
- column,
70
- value
71
- }: {
72
- schema: string;
73
- table: string;
74
- column: string;
75
- value: string;
76
- }): string;
238
+ declare function sqyrl(expr: string, whereGuard: WhereGuard): string;
239
+ declare function makeSqyrl(whereGuard: WhereGuard): (expr: string) => string;
77
240
  //#endregion
78
- export { outputSql, parseSql, sanitiseSql, sqyrl };
241
+ export { makeSqyrl, outputSql, parseSql, sanitiseSql, sqyrl };