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); // 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); // 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); const sql = 'SELECT * FROM SNOWFLAKE_SAMPLE_DATA.TPCH_SF1.CUSTOMER WHERE C_MKTSEGMENT = ?'; const params = ['AUTOMOBILE']; // First execution - will be cached by Snowflake const start1 = Date.now(); const result1 = await adapter.query(sql, params, 50); const time1 = Date.now() - start1; // Second execution - should hit Snowflake's cache const start2 = Date.now(); const result2 = await adapter.query(sql, params, 50); const time2 = Date.now() - start2; // 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; // Verify results expect(result1.rows.length).toBe(50); expect(result2.rows.length).toBe(50); expect(result3.rows.length).toBe(25); // All should indicate more rows available expect(result1.hasMoreRows).toBe(true); expect(result2.hasMoreRows).toBe(true); expect(result3.hasMoreRows).toBe(true); // Cache hits should be faster (allowing for some variance) console.info(`Query times: ${time1}ms, ${time2}ms, ${time3}ms`); // 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); // Query a small table without maxRows const result = await adapter.query( 'SELECT * FROM SNOWFLAKE_SAMPLE_DATA.TPCH_SF1.REGION' ); // 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); 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); // 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); 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 ); });