|
1 | 1 | /* eslint-disable sonarjs/no-duplicate-string */ |
2 | | -import { TableDomain } from '@teable/core'; |
| 2 | +import { CellValueType, DbFieldType, FieldType, TableDomain } from '@teable/core'; |
| 3 | +import type { IFieldVo } from '@teable/core'; |
| 4 | +import knex from 'knex'; |
3 | 5 | import { beforeEach, describe, expect, it } from 'vitest'; |
4 | 6 |
|
| 7 | +import { createFieldInstanceByVo } from '../../../features/field/model/factory'; |
5 | 8 | import type { IFieldSelectName } from '../../../features/record/query-builder/field-select.type'; |
6 | 9 | import type { ISelectFormulaConversionContext } from '../../../features/record/query-builder/sql-conversion.visitor'; |
| 10 | +import { PostgresProvider } from '../../postgres.provider'; |
7 | 11 | import { SelectQueryPostgres } from './select-query.postgres'; |
8 | 12 |
|
9 | 13 | describe('SelectQueryPostgres unit-aware date helpers', () => { |
@@ -132,9 +136,9 @@ describe('SelectQueryPostgres unit-aware date helpers', () => { |
132 | 136 | ); |
133 | 137 | }); |
134 | 138 |
|
135 | | - it('datetimeDiff subtracts timezone-normalized expressions', () => { |
136 | | - expect(tzQuery.datetimeDiff('start_col', 'end_col', `'day'`)).toBe( |
137 | | - `(EXTRACT(EPOCH FROM (${tz('end_col')} - ${tz('start_col')}))) / 86400` |
| 139 | + it('datetimeDiff subtracts the second argument from the first after timezone normalization', () => { |
| 140 | + expect(tzQuery.datetimeDiff('first_col', 'second_col', `'day'`)).toBe( |
| 141 | + `(EXTRACT(EPOCH FROM (${tz('first_col')} - ${tz('second_col')}))) / 86400` |
138 | 142 | ); |
139 | 143 | }); |
140 | 144 |
|
@@ -188,7 +192,7 @@ describe('SelectQueryPostgres unit-aware date helpers', () => { |
188 | 192 | expect(sql).toBe(`${localWrap('date_col')} + (${scaled}) * INTERVAL '1 ${unit}'`); |
189 | 193 | }); |
190 | 194 |
|
191 | | - const localDiffBase = `(EXTRACT(EPOCH FROM (${localWrap('date_end')} - ${localWrap('date_start')})))`; |
| 195 | + const localDiffBase = `(EXTRACT(EPOCH FROM (${localWrap('date_start')} - ${localWrap('date_end')})))`; |
192 | 196 | const datetimeDiffCases: Array<{ literal: string; expected: string }> = [ |
193 | 197 | { literal: 'millisecond', expected: `${localDiffBase} * 1000` }, |
194 | 198 | { literal: 'milliseconds', expected: `${localDiffBase} * 1000` }, |
@@ -267,3 +271,69 @@ describe('SelectQueryPostgres unit-aware date helpers', () => { |
267 | 271 | }); |
268 | 272 | }); |
269 | 273 | }); |
| 274 | + |
| 275 | +describe('Select formula boolean normalization', () => { |
| 276 | + const knexClient = knex({ client: 'pg' }); |
| 277 | + const provider = new PostgresProvider(knexClient); |
| 278 | + |
| 279 | + const booleanFieldVo: IFieldVo = { |
| 280 | + id: 'fldBoolean001', |
| 281 | + name: 'Boolean Flag', |
| 282 | + type: FieldType.Checkbox, |
| 283 | + options: {}, |
| 284 | + dbFieldName: 'bool_col', |
| 285 | + dbFieldType: DbFieldType.Boolean, |
| 286 | + cellValueType: CellValueType.Boolean, |
| 287 | + isLookup: false, |
| 288 | + isComputed: false, |
| 289 | + isMultipleCellValue: false, |
| 290 | + }; |
| 291 | + |
| 292 | + const textFieldVo: IFieldVo = { |
| 293 | + id: 'fldText001', |
| 294 | + name: 'Text Field', |
| 295 | + type: FieldType.SingleLineText, |
| 296 | + options: {}, |
| 297 | + dbFieldName: 'text_col', |
| 298 | + dbFieldType: DbFieldType.Text, |
| 299 | + cellValueType: CellValueType.String, |
| 300 | + isLookup: false, |
| 301 | + isComputed: false, |
| 302 | + isMultipleCellValue: false, |
| 303 | + }; |
| 304 | + |
| 305 | + const booleanField = createFieldInstanceByVo(booleanFieldVo); |
| 306 | + const textField = createFieldInstanceByVo(textFieldVo); |
| 307 | + |
| 308 | + const table = new TableDomain({ |
| 309 | + id: 'tblBoolean', |
| 310 | + name: 'Boolean Table', |
| 311 | + dbTableName: 'boolean_table', |
| 312 | + lastModifiedTime: '1970-01-01T00:00:00.000Z', |
| 313 | + fields: [booleanField, textField], |
| 314 | + }); |
| 315 | + |
| 316 | + const buildContext = (): ISelectFormulaConversionContext => ({ |
| 317 | + table, |
| 318 | + tableAlias: 'main', |
| 319 | + selectionMap: new Map<string, IFieldSelectName>([ |
| 320 | + [booleanField.id, '"main"."bool_col"'], |
| 321 | + [textField.id, '"main"."text_col"'], |
| 322 | + ]), |
| 323 | + timeZone: 'UTC', |
| 324 | + preferRawFieldReferences: true, |
| 325 | + }); |
| 326 | + |
| 327 | + it('casts boolean field references before PostgreSQL COALESCE', () => { |
| 328 | + const sql = provider.convertFormulaToSelectQuery('AND({fldBoolean001})', buildContext()); |
| 329 | + |
| 330 | + expect(sql).toContain('(("main"."bool_col"))::boolean'); |
| 331 | + expect(sql).toContain('COALESCE'); |
| 332 | + }); |
| 333 | + |
| 334 | + it('does not cast non-boolean field references', () => { |
| 335 | + const sql = provider.convertFormulaToSelectQuery('AND({fldText001})', buildContext()); |
| 336 | + |
| 337 | + expect(sql).not.toContain('::boolean'); |
| 338 | + }); |
| 339 | +}); |
0 commit comments