mirror of https://github.com/buster-so/buster.git
197 lines
6.4 KiB
TypeScript
197 lines
6.4 KiB
TypeScript
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
|
|
);
|
|
});
|