2025-07-12 04:54:34 +08:00
import { afterEach , beforeEach , describe , expect } from 'vitest' ;
import { SnowflakeAdapter } from '../../../src/adapters/snowflake' ;
import { DataSourceType } from '../../../src/types/credentials' ;
import type { SnowflakeCredentials } from '../../../src/types/credentials' ;
import { TEST_TIMEOUT , skipIfNoCredentials , testConfig } from '../../setup' ;
const testWithCredentials = skipIfNoCredentials ( 'snowflake' ) ;
describe ( 'Snowflake Memory Protection Tests' , ( ) = > {
let adapter : SnowflakeAdapter ;
let credentials : SnowflakeCredentials ;
beforeEach ( ( ) = > {
adapter = new SnowflakeAdapter ( ) ;
// Set up credentials once
if (
! testConfig . snowflake . account_id ||
! testConfig . snowflake . warehouse_id ||
! testConfig . snowflake . username ||
! testConfig . snowflake . password ||
! testConfig . snowflake . default_database
) {
throw new Error (
'TEST_SNOWFLAKE_ACCOUNT_ID, TEST_SNOWFLAKE_WAREHOUSE_ID, TEST_SNOWFLAKE_USERNAME, TEST_SNOWFLAKE_PASSWORD, and TEST_SNOWFLAKE_DATABASE are required for this test'
) ;
}
credentials = {
type : DataSourceType . Snowflake ,
account_id : testConfig.snowflake.account_id ,
warehouse_id : testConfig.snowflake.warehouse_id ,
username : testConfig.snowflake.username ,
password : testConfig.snowflake.password ,
default_database : testConfig.snowflake.default_database ,
default_schema : testConfig.snowflake.default_schema ,
role : testConfig.snowflake.role ,
} ;
} ) ;
afterEach ( async ( ) = > {
if ( adapter ) {
await adapter . close ( ) ;
}
} ) ;
testWithCredentials (
'should handle large result sets with maxRows without running out of memory' ,
async ( ) = > {
await adapter . initialize ( credentials ) ;
2025-07-13 06:32:47 +08:00
2025-07-12 04:54:34 +08:00
// NOTE: Due to Snowflake SDK limitations, we cannot truly stream results
// For now, we'll test with a smaller dataset to avoid OOM
// Query ORDERS table instead of LINEITEM (1.5M rows vs 6M rows)
const result = await adapter . query (
'SELECT * FROM SNOWFLAKE_SAMPLE_DATA.TPCH_SF1.ORDERS' ,
undefined ,
100 // Only fetch 100 rows
) ;
expect ( result . rows . length ) . toBe ( 100 ) ;
expect ( result . hasMoreRows ) . toBe ( true ) ;
expect ( result . rowCount ) . toBe ( 100 ) ;
2025-07-13 06:32:47 +08:00
2025-07-12 04:54:34 +08:00
// Verify we got the fields metadata
expect ( result . fields . length ) . toBeGreaterThan ( 0 ) ;
expect ( result . fields [ 0 ] ) . toHaveProperty ( 'name' ) ;
expect ( result . fields [ 0 ] ) . toHaveProperty ( 'type' ) ;
} ,
TEST_TIMEOUT
) ;
testWithCredentials (
'should preserve query caching when running the same query multiple times' ,
async ( ) = > {
await adapter . initialize ( credentials ) ;
2025-07-13 06:32:47 +08:00
2025-07-12 04:54:34 +08:00
const sql = 'SELECT * FROM SNOWFLAKE_SAMPLE_DATA.TPCH_SF1.CUSTOMER WHERE C_MKTSEGMENT = ?' ;
const params = [ 'AUTOMOBILE' ] ;
2025-07-13 06:32:47 +08:00
2025-07-12 04:54:34 +08:00
// First execution - will be cached by Snowflake
const start1 = Date . now ( ) ;
const result1 = await adapter . query ( sql , params , 50 ) ;
const time1 = Date . now ( ) - start1 ;
2025-07-13 06:32:47 +08:00
2025-07-12 04:54:34 +08:00
// Second execution - should hit Snowflake's cache
const start2 = Date . now ( ) ;
const result2 = await adapter . query ( sql , params , 50 ) ;
const time2 = Date . now ( ) - start2 ;
2025-07-13 06:32:47 +08:00
2025-07-12 04:54:34 +08:00
// Third execution with different maxRows - should still hit cache
const start3 = Date . now ( ) ;
const result3 = await adapter . query ( sql , params , 25 ) ;
const time3 = Date . now ( ) - start3 ;
2025-07-13 06:32:47 +08:00
2025-07-12 04:54:34 +08:00
// Verify results
expect ( result1 . rows . length ) . toBe ( 50 ) ;
expect ( result2 . rows . length ) . toBe ( 50 ) ;
expect ( result3 . rows . length ) . toBe ( 25 ) ;
2025-07-13 06:32:47 +08:00
2025-07-12 04:54:34 +08:00
// All should indicate more rows available
expect ( result1 . hasMoreRows ) . toBe ( true ) ;
expect ( result2 . hasMoreRows ) . toBe ( true ) ;
expect ( result3 . hasMoreRows ) . toBe ( true ) ;
2025-07-13 06:32:47 +08:00
2025-07-12 04:54:34 +08:00
// Cache hits should be faster (allowing for some variance)
console . info ( ` Query times: ${ time1 } ms, ${ time2 } ms, ${ time3 } ms ` ) ;
2025-07-13 06:32:47 +08:00
2025-07-12 04:54:34 +08:00
// The cached queries (2nd and 3rd) should generally be faster than the first
// We use a loose check because network latency can vary
const avgCachedTime = ( time2 + time3 ) / 2 ;
expect ( avgCachedTime ) . toBeLessThanOrEqual ( time1 * 1.5 ) ; // Allow 50% variance
} ,
TEST_TIMEOUT
) ;
testWithCredentials (
'should handle queries with no maxRows (fetch all results)' ,
async ( ) = > {
await adapter . initialize ( credentials ) ;
2025-07-13 06:32:47 +08:00
2025-07-12 04:54:34 +08:00
// Query a small table without maxRows
2025-07-13 06:32:47 +08:00
const result = await adapter . query ( 'SELECT * FROM SNOWFLAKE_SAMPLE_DATA.TPCH_SF1.REGION' ) ;
2025-07-12 04:54:34 +08:00
// REGION table has exactly 5 rows
expect ( result . rows . length ) . toBe ( 5 ) ;
expect ( result . hasMoreRows ) . toBe ( false ) ;
expect ( result . rowCount ) . toBe ( 5 ) ;
} ,
TEST_TIMEOUT
) ;
testWithCredentials (
'should handle maxRows=1 correctly' ,
async ( ) = > {
await adapter . initialize ( credentials ) ;
2025-07-13 06:32:47 +08:00
2025-07-12 04:54:34 +08:00
const result = await adapter . query (
'SELECT * FROM SNOWFLAKE_SAMPLE_DATA.TPCH_SF1.NATION ORDER BY N_NATIONKEY' ,
undefined ,
1
) ;
expect ( result . rows . length ) . toBe ( 1 ) ;
expect ( result . hasMoreRows ) . toBe ( true ) ;
expect ( result . rows [ 0 ] ) . toHaveProperty ( 'N_NATIONKEY' , 0 ) ; // First nation
} ,
TEST_TIMEOUT
) ;
testWithCredentials (
'should handle edge case where result set equals maxRows' ,
async ( ) = > {
await adapter . initialize ( credentials ) ;
2025-07-13 06:32:47 +08:00
2025-07-12 04:54:34 +08:00
// REGION table has exactly 5 rows
const result = await adapter . query (
'SELECT * FROM SNOWFLAKE_SAMPLE_DATA.TPCH_SF1.REGION' ,
undefined ,
5
) ;
expect ( result . rows . length ) . toBe ( 5 ) ;
expect ( result . hasMoreRows ) . toBe ( false ) ; // No more rows available
expect ( result . rowCount ) . toBe ( 5 ) ;
} ,
TEST_TIMEOUT
) ;
testWithCredentials (
'should handle complex queries with CTEs and maxRows' ,
async ( ) = > {
await adapter . initialize ( credentials ) ;
2025-07-13 06:32:47 +08:00
2025-07-12 04:54:34 +08:00
const sql = `
WITH high_value_orders AS (
SELECT O_CUSTKEY , SUM ( O_TOTALPRICE ) as total_spent
FROM SNOWFLAKE_SAMPLE_DATA . TPCH_SF1 . ORDERS
GROUP BY O_CUSTKEY
HAVING SUM ( O_TOTALPRICE ) > 500000
)
SELECT c . C_NAME , c . C_PHONE , h . total_spent
FROM SNOWFLAKE_SAMPLE_DATA . TPCH_SF1 . CUSTOMER c
JOIN high_value_orders h ON c . C_CUSTKEY = h . O_CUSTKEY
ORDER BY h . total_spent DESC
` ;
const result = await adapter . query ( sql , undefined , 10 ) ;
expect ( result . rows . length ) . toBe ( 10 ) ;
expect ( result . hasMoreRows ) . toBe ( true ) ;
expect ( result . fields . length ) . toBe ( 3 ) ; // C_NAME, C_PHONE, total_spent
} ,
TEST_TIMEOUT
) ;
2025-07-13 06:32:47 +08:00
} ) ;