squirreling 0.5.0 → 0.6.1

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.
@@ -1,11 +1,12 @@
1
1
  import { unknownFunctionError } from '../parseErrors.js'
2
2
  import { invalidContextError } from '../executionErrors.js'
3
3
  import {
4
+ aggregateError,
4
5
  argCountError,
5
6
  argValueError,
6
7
  castError,
7
8
  } from '../validationErrors.js'
8
- import { isMathFunc } from '../validation.js'
9
+ import { isAggregateFunc, isMathFunc } from '../validation.js'
9
10
  import { applyIntervalToDate } from './date.js'
10
11
  import { executeSelect } from './execute.js'
11
12
  import { evaluateMathFunc } from './math.js'
@@ -23,23 +24,24 @@ import { applyBinaryOp, stringify } from './utils.js'
23
24
  * @param {AsyncRow} params.row - The data row to evaluate against
24
25
  * @param {Record<string, AsyncDataSource>} params.tables
25
26
  * @param {number} [params.rowIndex] - 1-based row index for error reporting
27
+ * @param {AsyncRow[]} [params.rows] - Group of rows for aggregate functions
26
28
  * @returns {Promise<SqlPrimitive>} The result of the evaluation
27
29
  */
28
- export async function evaluateExpr({ node, row, tables, rowIndex }) {
30
+ export async function evaluateExpr({ node, row, tables, rowIndex, rows }) {
29
31
  if (node.type === 'literal') {
30
32
  return node.value
31
33
  }
32
34
 
33
35
  if (node.type === 'identifier') {
34
36
  // Try exact match first (handles both qualified and unqualified names)
35
- if (row[node.name]) {
36
- return row[node.name]()
37
+ if (row.cells[node.name]) {
38
+ return row.cells[node.name]()
37
39
  }
38
40
  // For qualified names like 'users.id', also try just the column part
39
41
  if (node.name.includes('.')) {
40
42
  const colName = node.name.split('.').pop()
41
- if (colName && row[colName]) {
42
- return row[colName]()
43
+ if (colName && row.cells[colName]) {
44
+ return row.cells[colName]()
43
45
  }
44
46
  }
45
47
  return null
@@ -47,29 +49,26 @@ export async function evaluateExpr({ node, row, tables, rowIndex }) {
47
49
 
48
50
  // Scalar subquery - returns a single value
49
51
  if (node.type === 'subquery') {
50
- const gen = executeSelect(node.subquery, tables)
51
- const first = await gen.next() // Start the generator
52
+ const gen = executeSelect({ select: node.subquery, tables })
53
+ const { value } = await gen.next() // Start the generator
52
54
  gen.return(undefined) // Stop further execution
53
- if (!first.value) return null
54
- /** @type {AsyncRow} */
55
- const firstRow = first.value
56
- const firstKey = Object.keys(firstRow)[0]
57
- return firstRow[firstKey]()
55
+ if (!value) return null
56
+ return value.cells[value.columns[0]]()
58
57
  }
59
58
 
60
59
  // Unary operators
61
60
  if (node.type === 'unary') {
62
61
  if (node.op === 'NOT') {
63
- return !await evaluateExpr({ node: node.argument, row, tables, rowIndex })
62
+ return !await evaluateExpr({ node: node.argument, row, tables, rowIndex, rows })
64
63
  }
65
64
  if (node.op === 'IS NULL') {
66
- return await evaluateExpr({ node: node.argument, row, tables, rowIndex }) == null
65
+ return await evaluateExpr({ node: node.argument, row, tables, rowIndex, rows }) == null
67
66
  }
68
67
  if (node.op === 'IS NOT NULL') {
69
- return await evaluateExpr({ node: node.argument, row, tables, rowIndex }) != null
68
+ return await evaluateExpr({ node: node.argument, row, tables, rowIndex, rows }) != null
70
69
  }
71
70
  if (node.op === '-') {
72
- const val = await evaluateExpr({ node: node.argument, row, tables, rowIndex })
71
+ const val = await evaluateExpr({ node: node.argument, row, tables, rowIndex, rows })
73
72
  if (val == null) return null
74
73
  return -val
75
74
  }
@@ -79,15 +78,15 @@ export async function evaluateExpr({ node, row, tables, rowIndex }) {
79
78
  if (node.type === 'binary') {
80
79
  // Handle date +/- interval at AST level
81
80
  if ((node.op === '+' || node.op === '-') && node.right.type === 'interval') {
82
- const dateVal = await evaluateExpr({ node: node.left, row, tables, rowIndex })
81
+ const dateVal = await evaluateExpr({ node: node.left, row, tables, rowIndex, rows })
83
82
  return applyIntervalToDate(dateVal, node.right.value, node.right.unit, node.op)
84
83
  }
85
84
  if (node.op === '+' && node.left.type === 'interval') {
86
- const dateVal = await evaluateExpr({ node: node.right, row, tables, rowIndex })
85
+ const dateVal = await evaluateExpr({ node: node.right, row, tables, rowIndex, rows })
87
86
  return applyIntervalToDate(dateVal, node.left.value, node.left.unit, '+')
88
87
  }
89
88
 
90
- const left = await evaluateExpr({ node: node.left, row, tables, rowIndex })
89
+ const left = await evaluateExpr({ node: node.left, row, tables, rowIndex, rows })
91
90
 
92
91
  // Short-circuit evaluation for AND and OR
93
92
  if (node.op === 'AND') {
@@ -97,15 +96,120 @@ export async function evaluateExpr({ node, row, tables, rowIndex }) {
97
96
  if (left) return true
98
97
  }
99
98
 
100
- const right = await evaluateExpr({ node: node.right, row, tables, rowIndex })
99
+ const right = await evaluateExpr({ node: node.right, row, tables, rowIndex, rows })
101
100
  return applyBinaryOp(node.op, left, right)
102
101
  }
103
102
 
104
103
  // Function calls
105
104
  if (node.type === 'function') {
106
105
  const funcName = node.name.toUpperCase()
106
+
107
+ // Handle aggregate functions
108
+ if (isAggregateFunc(funcName)) {
109
+ if (!rows) {
110
+ throw aggregateError({
111
+ funcName,
112
+ issue: 'requires GROUP BY or will act on the whole dataset',
113
+ })
114
+ }
115
+
116
+ // Check for star argument (COUNT(*))
117
+ if (node.args.length === 1 && node.args[0].type === 'identifier' && node.args[0].name === '*') {
118
+ if (funcName === 'COUNT') {
119
+ return rows.length
120
+ }
121
+ throw aggregateError({
122
+ funcName,
123
+ issue: '(*) is not supported, use a column name',
124
+ })
125
+ }
126
+
127
+ if (node.args.length !== 1) {
128
+ throw argCountError({
129
+ funcName,
130
+ expected: 1,
131
+ received: node.args.length,
132
+ positionStart: node.positionStart,
133
+ positionEnd: node.positionEnd,
134
+ rowNumber: rowIndex,
135
+ })
136
+ }
137
+
138
+ const argNode = node.args[0]
139
+
140
+ if (funcName === 'COUNT') {
141
+ if (node.distinct) {
142
+ const seen = new Set()
143
+ for (const r of rows) {
144
+ const v = await evaluateExpr({ node: argNode, row: r, tables })
145
+ if (v != null) seen.add(v)
146
+ }
147
+ return seen.size
148
+ }
149
+ let count = 0
150
+ for (const r of rows) {
151
+ const v = await evaluateExpr({ node: argNode, row: r, tables })
152
+ if (v != null) count++
153
+ }
154
+ return count
155
+ }
156
+
157
+ if (funcName === 'SUM' || funcName === 'AVG' || funcName === 'MIN' || funcName === 'MAX') {
158
+ let sum = 0
159
+ let count = 0
160
+ /** @type {number | null} */
161
+ let min = null
162
+ /** @type {number | null} */
163
+ let max = null
164
+
165
+ for (const r of rows) {
166
+ const raw = await evaluateExpr({ node: argNode, row: r, tables })
167
+ if (raw == null) continue
168
+ const num = Number(raw)
169
+ if (!Number.isFinite(num)) continue
170
+
171
+ if (count === 0) {
172
+ min = num
173
+ max = num
174
+ } else {
175
+ if (min == null || num < min) min = num
176
+ if (max == null || num > max) max = num
177
+ }
178
+ sum += num
179
+ count++
180
+ }
181
+
182
+ if (funcName === 'SUM') return sum
183
+ if (funcName === 'AVG') return count === 0 ? null : sum / count
184
+ if (funcName === 'MIN') return min
185
+ if (funcName === 'MAX') return max
186
+ }
187
+
188
+ if (funcName === 'JSON_ARRAYAGG') {
189
+ /** @type {SqlPrimitive[]} */
190
+ const values = []
191
+ if (node.distinct) {
192
+ const seen = new Set()
193
+ for (const r of rows) {
194
+ const v = await evaluateExpr({ node: argNode, row: r, tables })
195
+ const key = stringify(v)
196
+ if (!seen.has(key)) {
197
+ seen.add(key)
198
+ values.push(v)
199
+ }
200
+ }
201
+ } else {
202
+ for (const r of rows) {
203
+ const v = await evaluateExpr({ node: argNode, row: r, tables })
204
+ values.push(v)
205
+ }
206
+ }
207
+ return values
208
+ }
209
+ }
210
+
107
211
  /** @type {SqlPrimitive[]} */
108
- const args = await Promise.all(node.args.map(arg => evaluateExpr({ node: arg, row, tables, rowIndex })))
212
+ const args = await Promise.all(node.args.map(arg => evaluateExpr({ node: arg, row, tables, rowIndex, rows })))
109
213
 
110
214
  if (funcName === 'UPPER') {
111
215
  if (args.length !== 1) {
@@ -430,7 +534,7 @@ export async function evaluateExpr({ node, row, tables, rowIndex }) {
430
534
  }
431
535
 
432
536
  if (node.type === 'cast') {
433
- const val = await evaluateExpr({ node: node.expr, row, tables, rowIndex })
537
+ const val = await evaluateExpr({ node: node.expr, row, tables, rowIndex, rows })
434
538
  if (val == null) return null
435
539
  const toType = node.toType.toUpperCase()
436
540
  if (toType === 'TEXT' || toType === 'STRING' || toType === 'VARCHAR') {
@@ -473,62 +577,59 @@ export async function evaluateExpr({ node, row, tables, rowIndex }) {
473
577
 
474
578
  // IN and NOT IN with value lists
475
579
  if (node.type === 'in valuelist') {
476
- const exprVal = await evaluateExpr({ node: node.expr, row, tables, rowIndex })
580
+ const exprVal = await evaluateExpr({ node: node.expr, row, tables, rowIndex, rows })
477
581
  for (const valueNode of node.values) {
478
- const val = await evaluateExpr({ node: valueNode, row, tables, rowIndex })
582
+ const val = await evaluateExpr({ node: valueNode, row, tables, rowIndex, rows })
479
583
  if (exprVal === val) return true
480
584
  }
481
585
  return false
482
586
  }
483
587
  // IN with subqueries
484
588
  if (node.type === 'in') {
485
- const exprVal = await evaluateExpr({ node: node.expr, row, tables, rowIndex })
486
- const results = executeSelect(node.subquery, tables)
487
- /** @type {SqlPrimitive[]} */
488
- const values = []
589
+ const exprVal = await evaluateExpr({ node: node.expr, row, tables, rowIndex, rows })
590
+ const results = executeSelect({ select: node.subquery, tables })
489
591
  for await (const resRow of results) {
490
- const firstKey = Object.keys(resRow)[0]
491
- const val = await resRow[firstKey]()
492
- values.push(val)
592
+ const value = await resRow.cells[resRow.columns[0]]()
593
+ if (exprVal === value) return true
493
594
  }
494
- return values.includes(exprVal)
595
+ return false
495
596
  }
496
597
 
497
598
  // EXISTS and NOT EXISTS with subqueries
498
599
  if (node.type === 'exists') {
499
- const results = await executeSelect(node.subquery, tables).next()
600
+ const results = await executeSelect({ select: node.subquery, tables }).next()
500
601
  return results.done === false
501
602
  }
502
603
  if (node.type === 'not exists') {
503
- const results = await executeSelect(node.subquery, tables).next()
604
+ const results = await executeSelect({ select: node.subquery, tables }).next()
504
605
  return results.done === true
505
606
  }
506
607
 
507
608
  // CASE expressions
508
609
  if (node.type === 'case') {
509
610
  // For simple CASE: evaluate the case expression once
510
- const caseValue = node.caseExpr && await evaluateExpr({ node: node.caseExpr, row, tables, rowIndex })
611
+ const caseValue = node.caseExpr && await evaluateExpr({ node: node.caseExpr, row, tables, rowIndex, rows })
511
612
 
512
613
  // Iterate through WHEN clauses
513
614
  for (const whenClause of node.whenClauses) {
514
615
  let conditionResult
515
616
  if (caseValue !== undefined) {
516
617
  // Simple CASE: compare caseValue with condition
517
- const whenValue = await evaluateExpr({ node: whenClause.condition, row, tables, rowIndex })
618
+ const whenValue = await evaluateExpr({ node: whenClause.condition, row, tables, rowIndex, rows })
518
619
  conditionResult = caseValue === whenValue
519
620
  } else {
520
621
  // Searched CASE: evaluate condition as boolean
521
- conditionResult = await evaluateExpr({ node: whenClause.condition, row, tables, rowIndex })
622
+ conditionResult = await evaluateExpr({ node: whenClause.condition, row, tables, rowIndex, rows })
522
623
  }
523
624
 
524
625
  if (conditionResult) {
525
- return evaluateExpr({ node: whenClause.result, row, tables, rowIndex })
626
+ return evaluateExpr({ node: whenClause.result, row, tables, rowIndex, rows })
526
627
  }
527
628
  }
528
629
 
529
630
  // No WHEN clause matched, return ELSE result or NULL
530
631
  if (node.elseResult) {
531
- return evaluateExpr({ node: node.elseResult, row, tables, rowIndex })
632
+ return evaluateExpr({ node: node.elseResult, row, tables, rowIndex, rows })
532
633
  }
533
634
  return null
534
635
  }
@@ -4,7 +4,7 @@ import { evaluateExpr } from './expression.js'
4
4
  import { stringify } from './utils.js'
5
5
 
6
6
  /**
7
- * @import { AsyncRow, AsyncDataSource, JoinClause, ExprNode } from '../types.js'
7
+ * @import { AsyncRow, AsyncDataSource, JoinClause, ExprNode, AsyncCells } from '../types.js'
8
8
  */
9
9
 
10
10
  /**
@@ -30,7 +30,7 @@ export async function executeJoins(leftSource, joins, leftTableName, tables) {
30
30
  // Buffer right rows for hash index (required for hash join)
31
31
  /** @type {AsyncRow[]} */
32
32
  const rightRows = []
33
- for await (const row of rightSource.getRows()) {
33
+ for await (const row of rightSource.scan({})) {
34
34
  rightRows.push(row)
35
35
  }
36
36
 
@@ -39,14 +39,16 @@ export async function executeJoins(leftSource, joins, leftTableName, tables) {
39
39
 
40
40
  // Return streaming data source - left rows stream through without buffering
41
41
  return {
42
- async *getRows() {
42
+ async *scan(options) {
43
+ const { signal } = options
43
44
  yield* hashJoin({
44
- leftRows: leftSource.getRows(), // Stream directly, not buffered
45
+ leftRows: leftSource.scan(options), // Stream directly, not buffered
45
46
  rightRows,
46
47
  join,
47
48
  leftTable: currentLeftTable,
48
49
  rightTable: rightTableName,
49
50
  tables,
51
+ signal,
50
52
  })
51
53
  },
52
54
  }
@@ -55,7 +57,7 @@ export async function executeJoins(leftSource, joins, leftTableName, tables) {
55
57
  // Multiple joins: buffer intermediate results, stream final join
56
58
  /** @type {AsyncRow[]} */
57
59
  let leftRows = []
58
- for await (const row of leftSource.getRows()) {
60
+ for await (const row of leftSource.scan({})) {
59
61
  leftRows.push(row)
60
62
  }
61
63
 
@@ -69,7 +71,7 @@ export async function executeJoins(leftSource, joins, leftTableName, tables) {
69
71
 
70
72
  /** @type {AsyncRow[]} */
71
73
  const rightRows = []
72
- for await (const row of rightSource.getRows()) {
74
+ for await (const row of rightSource.scan({})) {
73
75
  rightRows.push(row)
74
76
  }
75
77
 
@@ -105,7 +107,7 @@ export async function executeJoins(leftSource, joins, leftTableName, tables) {
105
107
 
106
108
  /** @type {AsyncRow[]} */
107
109
  const rightRows = []
108
- for await (const row of rightSource.getRows()) {
110
+ for await (const row of rightSource.scan({})) {
109
111
  rightRows.push(row)
110
112
  }
111
113
 
@@ -113,7 +115,8 @@ export async function executeJoins(leftSource, joins, leftTableName, tables) {
113
115
  const lastRightTableName = lastJoin.alias ?? lastJoin.table
114
116
 
115
117
  return {
116
- async *getRows() {
118
+ async *scan(options) {
119
+ const { signal } = options
117
120
  yield* hashJoin({
118
121
  leftRows,
119
122
  rightRows,
@@ -121,6 +124,7 @@ export async function executeJoins(leftSource, joins, leftTableName, tables) {
121
124
  leftTable: currentLeftTable,
122
125
  rightTable: lastRightTableName,
123
126
  tables,
127
+ signal,
124
128
  })
125
129
  },
126
130
  }
@@ -172,12 +176,12 @@ function extractJoinKeys(onCondition, leftTable, rightTable) {
172
176
  * @returns {AsyncRow}
173
177
  */
174
178
  function createNullRow(columnNames) {
175
- /** @type {AsyncRow} */
176
- const row = {}
179
+ /** @type {AsyncCells} */
180
+ const cells = {}
177
181
  for (const col of columnNames) {
178
- row[col] = () => Promise.resolve(null)
182
+ cells[col] = () => Promise.resolve(null)
179
183
  }
180
- return row
184
+ return { columns: columnNames, cells }
181
185
  }
182
186
 
183
187
  /**
@@ -190,33 +194,35 @@ function createNullRow(columnNames) {
190
194
  * @returns {AsyncRow}
191
195
  */
192
196
  function mergeRows(leftRow, rightRow, leftTable, rightTable) {
193
- /** @type {AsyncRow} */
194
- const merged = {}
197
+ const columns = []
198
+ /** @type {AsyncCells} */
199
+ const cells = {}
195
200
 
196
201
  // Add left table columns with prefix
197
- for (const [key, cell] of Object.entries(leftRow)) {
202
+ for (const [key, cell] of Object.entries(leftRow.cells)) {
198
203
  // Skip already-prefixed keys (from previous joins)
199
204
  if (!key.includes('.')) {
200
- merged[`${leftTable}.${key}`] = cell
201
- } else {
202
- merged[key] = cell
205
+ const alias = `${leftTable}.${key}`
206
+ cells[alias] = cell
203
207
  }
204
- // Also keep unqualified name for convenience (may be overwritten if ambiguous)
205
- merged[key] = cell
208
+ // Also keep unqualified name for convenience
209
+ columns.push(key)
210
+ cells[key] = cell
206
211
  }
207
212
 
208
213
  // Add right table columns with prefix
209
- for (const [key, cell] of Object.entries(rightRow)) {
214
+ for (const [key, cell] of Object.entries(rightRow.cells)) {
210
215
  if (!key.includes('.')) {
211
- merged[`${rightTable}.${key}`] = cell
216
+ cells[`${rightTable}.${key}`] = cell
212
217
  } else {
213
- merged[key] = cell
218
+ cells[key] = cell
214
219
  }
215
220
  // Unqualified name (overwrites if same name exists in left table)
216
- merged[key] = cell
221
+ columns.push(key)
222
+ cells[key] = cell
217
223
  }
218
224
 
219
- return merged
225
+ return { columns, cells }
220
226
  }
221
227
 
222
228
  /**
@@ -230,9 +236,10 @@ function mergeRows(leftRow, rightRow, leftTable, rightTable) {
230
236
  * @param {string} params.leftTable - name of left table (for column prefixing)
231
237
  * @param {string} params.rightTable - name of right table (for column prefixing, may be alias)
232
238
  * @param {Record<string, AsyncDataSource>} params.tables - all tables for expression evaluation
239
+ * @param {AbortSignal} [params.signal] - abort signal for cancellation
233
240
  * @yields {AsyncRow} joined rows
234
241
  */
235
- async function* hashJoin({ leftRows, rightRows, join, leftTable, rightTable, tables }) {
242
+ async function* hashJoin({ leftRows, rightRows, join, leftTable, rightTable, tables, signal }) {
236
243
  const { joinType, on: onCondition } = join
237
244
 
238
245
  if (!onCondition) {
@@ -245,7 +252,7 @@ async function* hashJoin({ leftRows, rightRows, join, leftTable, rightTable, tab
245
252
  const keys = extractJoinKeys(onCondition, leftTable, rightTable)
246
253
 
247
254
  // Get column names for NULL row generation (right side is always buffered)
248
- const rightCols = rightRows.length ? Object.keys(rightRows[0]) : []
255
+ const rightCols = rightRows.length ? rightRows[0].columns : []
249
256
  const rightPrefixedCols = rightCols.flatMap(col =>
250
257
  col.includes('.') ? [col] : [`${rightTable}.${col}`, col]
251
258
  )
@@ -279,10 +286,10 @@ async function* hashJoin({ leftRows, rightRows, join, leftTable, rightTable, tab
279
286
 
280
287
  // PROBE PHASE: Stream through left rows, yield matches immediately
281
288
  for await (const leftRow of leftRows) {
289
+ if (signal?.aborted) break
282
290
  // Capture left column info from first row (for NULL row generation)
283
291
  if (!leftPrefixedCols) {
284
- const leftCols = Object.keys(leftRow)
285
- leftPrefixedCols = leftCols.flatMap(col =>
292
+ leftPrefixedCols = leftRow.columns.flatMap(col =>
286
293
  col.includes('.') ? [col] : [`${leftTable}.${col}`, col]
287
294
  )
288
295
  }
@@ -321,10 +328,10 @@ async function* hashJoin({ leftRows, rightRows, join, leftTable, rightTable, tab
321
328
  const matchedRightRows = joinType === 'RIGHT' || joinType === 'FULL' ? new Set() : null
322
329
 
323
330
  for await (const leftRow of leftRows) {
331
+ if (signal?.aborted) break
324
332
  // Capture left column info from first row (for NULL row generation)
325
333
  if (!leftPrefixedCols) {
326
- const leftCols = Object.keys(leftRow)
327
- leftPrefixedCols = leftCols.flatMap(col =>
334
+ leftPrefixedCols = leftRow.columns.flatMap(col =>
328
335
  col.includes('.') ? [col] : [`${leftTable}.${col}`, col]
329
336
  )
330
337
  }
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @import { SqlPrimitive } from '../types.js'
2
+ * @import { MathFunc, SqlPrimitive } from '../types.js'
3
3
  */
4
4
  import { argCountError } from '../validationErrors.js'
5
5
 
@@ -7,7 +7,7 @@ import { argCountError } from '../validationErrors.js'
7
7
  * Evaluate a math function
8
8
  *
9
9
  * @param {Object} options
10
- * @param {string} options.funcName - Uppercase function name
10
+ * @param {MathFunc} options.funcName - Uppercase function name
11
11
  * @param {SqlPrimitive[]} options.args - Function arguments
12
12
  * @param {number} options.positionStart - Start position in query
13
13
  * @param {number} options.positionEnd - End position in query
@@ -161,5 +161,180 @@ export function evaluateMathFunc({ funcName, args, positionStart, positionEnd, r
161
161
  return Math.sqrt(Number(val))
162
162
  }
163
163
 
164
- return undefined
164
+ if (funcName === 'SIN') {
165
+ if (args.length !== 1) {
166
+ throw argCountError({
167
+ funcName: 'SIN',
168
+ expected: 1,
169
+ received: args.length,
170
+ positionStart,
171
+ positionEnd,
172
+ rowNumber,
173
+ })
174
+ }
175
+ const val = args[0]
176
+ if (val == null) return null
177
+ return Math.sin(Number(val))
178
+ }
179
+
180
+ if (funcName === 'COS') {
181
+ if (args.length !== 1) {
182
+ throw argCountError({
183
+ funcName: 'COS',
184
+ expected: 1,
185
+ received: args.length,
186
+ positionStart,
187
+ positionEnd,
188
+ rowNumber,
189
+ })
190
+ }
191
+ const val = args[0]
192
+ if (val == null) return null
193
+ return Math.cos(Number(val))
194
+ }
195
+
196
+ if (funcName === 'TAN') {
197
+ if (args.length !== 1) {
198
+ throw argCountError({
199
+ funcName: 'TAN',
200
+ expected: 1,
201
+ received: args.length,
202
+ positionStart,
203
+ positionEnd,
204
+ rowNumber,
205
+ })
206
+ }
207
+ const val = args[0]
208
+ if (val == null) return null
209
+ return Math.tan(Number(val))
210
+ }
211
+
212
+ if (funcName === 'COT') {
213
+ if (args.length !== 1) {
214
+ throw argCountError({
215
+ funcName: 'COT',
216
+ expected: 1,
217
+ received: args.length,
218
+ positionStart,
219
+ positionEnd,
220
+ rowNumber,
221
+ })
222
+ }
223
+ const val = args[0]
224
+ if (val == null) return null
225
+ return 1 / Math.tan(Number(val))
226
+ }
227
+
228
+ if (funcName === 'ASIN') {
229
+ if (args.length !== 1) {
230
+ throw argCountError({
231
+ funcName: 'ASIN',
232
+ expected: 1,
233
+ received: args.length,
234
+ positionStart,
235
+ positionEnd,
236
+ rowNumber,
237
+ })
238
+ }
239
+ const val = args[0]
240
+ if (val == null) return null
241
+ return Math.asin(Number(val))
242
+ }
243
+
244
+ if (funcName === 'ACOS') {
245
+ if (args.length !== 1) {
246
+ throw argCountError({
247
+ funcName: 'ACOS',
248
+ expected: 1,
249
+ received: args.length,
250
+ positionStart,
251
+ positionEnd,
252
+ rowNumber,
253
+ })
254
+ }
255
+ const val = args[0]
256
+ if (val == null) return null
257
+ return Math.acos(Number(val))
258
+ }
259
+
260
+ if (funcName === 'ATAN') {
261
+ if (args.length !== 1) {
262
+ throw argCountError({
263
+ funcName: 'ATAN',
264
+ expected: 1,
265
+ received: args.length,
266
+ positionStart,
267
+ positionEnd,
268
+ rowNumber,
269
+ })
270
+ }
271
+ const val = args[0]
272
+ if (val == null) return null
273
+ return Math.atan(Number(val))
274
+ }
275
+
276
+ if (funcName === 'ATAN2') {
277
+ if (args.length !== 2) {
278
+ throw argCountError({
279
+ funcName: 'ATAN2',
280
+ expected: 2,
281
+ received: args.length,
282
+ positionStart,
283
+ positionEnd,
284
+ rowNumber,
285
+ })
286
+ }
287
+ const y = args[0]
288
+ const x = args[1]
289
+ if (y == null || x == null) return null
290
+ return Math.atan2(Number(y), Number(x))
291
+ }
292
+
293
+ if (funcName === 'DEGREES') {
294
+ if (args.length !== 1) {
295
+ throw argCountError({
296
+ funcName: 'DEGREES',
297
+ expected: 1,
298
+ received: args.length,
299
+ positionStart,
300
+ positionEnd,
301
+ rowNumber,
302
+ })
303
+ }
304
+ const val = args[0]
305
+ if (val == null) return null
306
+ return Number(val) * 180 / Math.PI
307
+ }
308
+
309
+ if (funcName === 'RADIANS') {
310
+ if (args.length !== 1) {
311
+ throw argCountError({
312
+ funcName: 'RADIANS',
313
+ expected: 1,
314
+ received: args.length,
315
+ positionStart,
316
+ positionEnd,
317
+ rowNumber,
318
+ })
319
+ }
320
+ const val = args[0]
321
+ if (val == null) return null
322
+ return Number(val) * Math.PI / 180
323
+ }
324
+
325
+ if (funcName === 'PI') {
326
+ if (args.length !== 0) {
327
+ throw argCountError({
328
+ funcName: 'PI',
329
+ expected: 0,
330
+ received: args.length,
331
+ positionStart,
332
+ positionEnd,
333
+ rowNumber,
334
+ })
335
+ }
336
+ return Math.PI
337
+ }
338
+
339
+ return null
165
340
  }