UNPKG

bigquery-client

Version:

A feature-rich Node.js client for Google BigQuery with support for CRUD operations, transactions, query building, and advanced features like aggregate functions, pagination, and logging.

931 lines (930 loc) 37.9 kB
"use strict"; /** * @fileoverview BigQuery Client ORM - A comprehensive TypeScript ORM for Google BigQuery * @version 1.0.6 * @author Pravin Jadhav * @description This module provides a high-level interface for interacting with Google BigQuery, * including CRUD operations, query caching, metrics collection, and advanced features like * materialized views and partitioned tables. */ Object.defineProperty(exports, "__esModule", { value: true }); exports.BigQueryClient = void 0; const bigquery_1 = require("@google-cloud/bigquery"); const QueryBuilder_1 = require("./QueryBuilder"); const logging_1 = require("../lib/logging"); const constants_1 = require("../config/constants"); const errors_1 = require("../errors"); const validation_1 = require("../lib/validation"); const cache_1 = require("../lib/cache"); const metrics_1 = require("../lib/metrics"); /** * List of supported SQL aggregate functions for query validation * @constant {string[]} */ const aggregateFunctions = [ "SUM", "AVG", "COUNT", "MIN", "MAX", "ARRAY_AGG", "STRING_AGG", "BIT_AND", "BIT_OR", "BIT_XOR", "LOGICAL_AND", "LOGICAL_OR", "ANY_VALUE", "COUNTIF", "GROUPING", "MAX_BY", "MIN_BY" ]; /** * BigQuery Client ORM - Main class for BigQuery operations * * This class provides a comprehensive interface for interacting with Google BigQuery, * including CRUD operations, query caching, metrics collection, SQL injection protection, * and advanced features like materialized views and partitioned tables. * * @class BigQueryClient * @example * ```typescript * const client = new BigQueryClient({ * projectId: 'my-project', * datasetId: 'my-dataset', * enableLogging: true, * enableCache: true * }); * * // Execute a SELECT query * const result = await client.select({ * table: 'users', * columns: ['id', 'name', 'email'], * where: { active: true }, * limit: 10 * }); * ``` */ class BigQueryClient { /** * Creates a new BigQueryClient instance * * @param {BigQueryClientConfig} config - Configuration object for the client * @example * ```typescript * const client = new BigQueryClient({ * projectId: 'my-gcp-project', * datasetId: 'analytics_data', * enableLogging: true, * enableCache: true, * cacheTtl: 600000, // 10 minutes * cacheMaxSize: 500 * }); * ``` */ constructor(config) { this.bigQuery = new bigquery_1.BigQuery({ projectId: config.projectId }); this.projectId = config.projectId; this.datasetId = config.datasetId; this.logger = new logging_1.Logger(config.enableLogging || false); this.queryCache = new cache_1.QueryCache({ enabled: config.enableCache || false, ttl: config.cacheTtl || 300000, // 5 minutes default maxSize: config.cacheMaxSize || 1000 }); this.metricsCollector = new metrics_1.MetricsCollector(); } /** * Formats a table name to include the full BigQuery path * * @private * @param {string} table - The table name to format * @returns {string} Fully qualified table name in format `project.dataset.table` * @example * ```typescript * // Input: 'users' * // Output: '`my-project.my-dataset.users`' * ``` */ formatTableName(table) { return `\`${this.projectId}.${this.datasetId}.${table}\``; } /** * Executes a raw SQL query on BigQuery with caching and validation * * This method provides the core query execution functionality with built-in: * - SQL injection protection * - Parameter validation * - Query result caching * - Performance metrics collection * - Error handling and logging * * @template T - The expected type of the query result data * @param {string} sql - The SQL query string to execute * @param {any[]} [params] - Optional parameters for parameterized queries * @returns {Promise<QueryResult<T>>} Promise resolving to query results with metadata * @throws {BigQueryError} When query validation fails or execution errors occur * * @example * ```typescript * // Simple query * const result = await client.query('SELECT * FROM users WHERE active = ?', [true]); * * // With type safety * interface User { id: number; name: string; email: string; } * const users = await client.query<User>('SELECT id, name, email FROM users'); * * // Access results * console.log(result.data); // Query results * console.log(result.metadata.executionTime); // Performance metrics * console.log(result.metadata.cacheHit); // Whether result was cached * ``` */ async query(sql, params) { try { // Validate query for SQL injection and parameter safety validation_1.QueryValidator.validateQuery(sql); if (params) validation_1.QueryValidator.validateParameters(params); // Check cache for existing results const cacheKey = this.generateCacheKey(sql, params); const cachedResult = await this.queryCache.get(cacheKey); if (cachedResult) { return { ...cachedResult, metadata: { ...cachedResult.metadata, cacheHit: true } }; } // Execute query and measure performance const startTime = Date.now(); const result = await this.executeQuery(sql, params); const executionTime = Date.now() - startTime; // Build result object with metadata const queryResult = { data: result[0], metadata: { totalRows: result[0].length, schema: result[1], executionTime, bytesProcessed: result[2]?.totalBytesProcessed || 0 } }; // Cache result and record metrics await this.queryCache.set(cacheKey, queryResult); this.metricsCollector.recordQuery({ executionTime, bytesProcessed: queryResult.metadata.bytesProcessed, rowsAffected: queryResult.data.length, cacheHit: false, timestamp: new Date().toISOString() }); return queryResult; } catch (error) { this.metricsCollector.recordError(error); throw new errors_1.BigQueryError(error instanceof Error ? error.message : 'Unknown error', errors_1.ErrorType.QUERY_ERROR, error); } } /** * Executes a dry run of a SQL query to analyze its execution plan * * This method performs query validation and cost estimation without actually * executing the query or processing any data. Useful for: * - Query optimization * - Cost estimation * - Syntax validation * - Performance planning * * @param {string} sql - The SQL query string to analyze * @param {any[]} [params] - Optional parameters for parameterized queries * @returns {Promise<any>} Promise resolving to query job metadata and execution plan * @throws {Error} When dry run execution fails * * @example * ```typescript * const plan = await client.explain('SELECT * FROM large_table WHERE date > ?', ['2023-01-01']); * console.log(plan.statistics.totalBytesProcessed); // Estimated bytes * console.log(plan.statistics.creationTime); // Query creation time * ``` */ async explain(sql, params) { try { const options = { query: sql, dryRun: true }; if (params) { options.params = params; } const [job] = await this.bigQuery.createQueryJob(options); return job.metadata; } catch (error) { this.logger.logError(error); throw error; } } /** * Executes a SELECT query with advanced features like JOINs, aggregations, and filtering * * This method provides a high-level interface for SELECT operations with support for: * - Multi-table JOINs with automatic aliasing * - Aggregate functions (SUM, COUNT, AVG, etc.) * - Complex WHERE conditions * - GROUP BY and ORDER BY clauses * - LIMIT and OFFSET for pagination * - Automatic SQL injection protection * * @param {SelectOptions} options - Configuration object for the SELECT query * @returns {Promise<{success: boolean, message: string, data: any[]}>} Promise resolving to query results * @throws {BigQueryError} When query construction or execution fails * * @example * ```typescript * // Simple SELECT * const users = await client.select({ * table: 'users', * columns: ['id', 'name', 'email'], * where: { active: true }, * limit: 10 * }); * * // Complex SELECT with JOINs and aggregation * const report = await client.select({ * table: 'orders', * columns: { * orders: ['id', 'total'], * users: ['name', 'email'] * }, * joins: [{ * table: 'users', * on: { 'orders.user_id': 'users.id' }, * type: 'INNER' * }], * where: { 'orders.status': 'completed' }, * orderBy: [{ column: 'total', direction: 'DESC' }] * }); * ``` */ async select(options) { try { const { table, columns = {}, joins = [], where, groupBy, orderBy, limit, offset } = options; // Generate alias for the main table (e.g., users → u) const mainAlias = table[0]; // First letter aliasing const formattedMainTable = `${this.formatTableName(table)} AS ${mainAlias}`; // Initialize QueryBuilder const qb = new QueryBuilder_1.QueryBuilder(formattedMainTable); // Store table aliases const aliasMap = { [table]: mainAlias }; // Normalize columns: Convert array format to object format if necessary const normalizedColumns = Array.isArray(columns) ? { [table]: columns } : columns; // Handle column selection with correct table aliasing const selectedColumns = Object.entries(normalizedColumns).flatMap(([tableRef, cols]) => Array.isArray(cols) ? cols.map(col => { // Check if the column contains an aggregate function and an alias const match = col.match(/^(\w+)\((.*?)\)(?:\s+AS\s+(\w+))?$/i); // Extract function, column, and alias if (match) { const funcName = match[1].toUpperCase(); // Aggregate function name const columnName = match[2]; // Column inside function let userDefinedAlias = match[3]; // Extract alias if provided if (constants_1.AGGREGATE_FUNCTIONS.has(funcName)) { // Ensure table alias is applied correctly const formattedColumn = columnName === "*" ? "*" : `${aliasMap[tableRef] || tableRef[0]}.${columnName}`; // If no alias is provided, generate a meaningful alias if (!userDefinedAlias) { userDefinedAlias = `${funcName.toLowerCase()}_${columnName.replace(/\W/g, '')}`; } return `${funcName}(${formattedColumn}) AS ${userDefinedAlias}`; } } // Apply table alias for regular columns return `${aliasMap[tableRef] || tableRef[0]}.${col}`; }) : []); qb.select(selectedColumns); // Process joins with correct aliasing for (const join of joins) { const joinAlias = join.table[0]; // Generate alias (e.g., orders → o) aliasMap[join.table] = joinAlias; // Proper join condition references const joinCondition = Object.entries(join.on).reduce((acc, [left, right]) => { const leftAlias = aliasMap[left.split('.')[0]] || mainAlias; const rightAlias = aliasMap[join.table] || joinAlias; return { ...acc, [`${leftAlias}.${left.split('.').pop()}`]: `${rightAlias}.${right.split('.').pop()}` }; }, {}); qb.join(`${this.formatTableName(join.table)} AS ${joinAlias}`, joinCondition, join.type || 'INNER'); } // Update WHERE clause to use aliases if (where) { const aliasedWhere = Object.fromEntries(Object.entries(where).map(([key, value]) => { const [tableRef, colName] = key.includes('.') ? key.split('.') : [table, key]; const tableAlias = aliasMap[tableRef] || aliasMap[table] || mainAlias; return [`${tableAlias}.${colName}`, value]; })); qb.where(aliasedWhere); } // Handle GROUP BY clause if (groupBy) { qb.groupBy(groupBy.map(col => { const [tableRef, colName] = col.includes('.') ? col.split('.') : [table, col]; return `${aliasMap[tableRef] || mainAlias}.${colName}`; })); } // Handle ORDER BY clause with correct aliasing if (orderBy) { // Extract column aliases from the selected columns const columnAliases = {}; selectedColumns.forEach(col => { const match = col.match(/AS (\w+)$/i); // Extract alias (e.g., `SUM(t.price) AS total_price`) if (match) { columnAliases[match[1]] = col.split(" AS ")[0]; // Map alias to its original column } }); qb.orderBy(orderBy.map((col) => { let tableRef, colName; if (typeof col === "string") { [tableRef, colName] = col.includes('.') ? col.split('.') : [null, col]; } else if (col.column) { [tableRef, colName] = col.column.includes('.') ? col.column.split('.') : [null, col.column]; } else { throw new Error("Invalid orderBy format"); } // If the column has an alias, use the alias directly if (columnAliases[colName]) { return { column: colName, direction: col.direction || 'ASC' }; } // If table name is provided, map it correctly if (tableRef) { return { column: `${aliasMap[tableRef] || tableRef}.${colName}`, direction: col.direction || 'ASC' }; } // Default case: no table name, use column as-is return { column: colName, direction: col.direction || 'ASC' }; })); } if (limit) qb.limit(limit); if (offset) qb.offset(offset); // Execute the query const { query, params } = qb.build(); console.log("🚀 Executing SELECT Query:", query); console.log("🔍 With Parameters:", params); const [result] = await this.bigQuery.query({ query, params }); console.log("✅ BigQuery Select Execution Result:", result); return result.length > 0 ? { success: true, message: "Select successful", data: result } : { success: false, message: "No records found", data: [] }; } catch (error) { this.logger.logError(error); throw error; } } /** * Inserts multiple rows into a BigQuery table with automatic validation * * This method provides a safe and efficient way to insert data with: * - Automatic SQL injection protection * - Batch processing for multiple rows * - Schema validation * - Error handling and logging * - Transaction-like behavior * * @param {InsertOptions} options - Configuration object containing table and row data * @returns {Promise<{success: boolean, message: string, affectedRows: number}>} Promise resolving to insert results * @throws {BigQueryError} When validation fails or insert operation encounters errors * * @example * ```typescript * // Insert single user * const result = await client.insert({ * table: 'users', * rows: [{ * name: 'John Doe', * email: 'john@example.com', * active: true, * created_at: new Date().toISOString() * }] * }); * * // Insert multiple users * const bulkResult = await client.insert({ * table: 'users', * rows: [ * { name: 'Alice', email: 'alice@example.com' }, * { name: 'Bob', email: 'bob@example.com' }, * { name: 'Charlie', email: 'charlie@example.com' } * ] * }); * * console.log(`Inserted ${bulkResult.affectedRows} rows`); * ``` */ async insert(options) { try { const { table, rows } = options; const qb = new QueryBuilder_1.QueryBuilder(this.formatTableName(table)); qb.insert(rows); const { query, params } = qb.build(); // Execute insert query await this.bigQuery.query({ query, params }); // Return success if no error was thrown return { success: true, message: "Insert successful", affectedRows: rows.length }; } catch (error) { this.logger.logError(error); return { success: false, message: "Insert failed", affectedRows: 0 }; } } /** * Updates existing rows in a BigQuery table with conditional filtering * * This method provides secure and efficient row updates with: * - Mandatory WHERE clause to prevent accidental full table updates * - Automatic SQL injection protection * - Field validation and type checking * - Atomic update operations * - Comprehensive error handling * * @param {UpdateOptions} options - Configuration object containing table, update fields, and conditions * @returns {Promise<{success: boolean, message: string, affectedRows: number}>} Promise resolving to update results * @throws {BigQueryError} When validation fails, WHERE clause is empty, or update operation fails * * @example * ```typescript * // Update user status * const result = await client.update({ * table: 'users', * set: { * active: false, * updated_at: new Date().toISOString(), * status: 'inactive' * }, * where: { * user_id: 123, * email: 'user@example.com' * } * }); * * // Update with complex conditions * const bulkUpdate = await client.update({ * table: 'orders', * set: { status: 'shipped', shipped_date: '2023-12-01' }, * where: { status: 'processing', priority: 'high' } * }); * * console.log(`Updated ${result.affectedRows} rows`); * ``` */ async update(options) { try { const { table, set, where } = options; if (!Object.keys(set).length) throw new Error("Update failed: No fields provided to update."); if (!Object.keys(where).length) throw new Error("Update failed: WHERE clause cannot be empty."); // ✅ Ensure proper formatting of table name const fullTableName = this.formatTableName(table); const qb = new QueryBuilder_1.QueryBuilder(fullTableName); qb.update(set).where(where); const { query, params } = qb.build(); // Execute update query await this.bigQuery.query({ query, params }); // Return success if no error was thrown return { success: true, message: "Update successful", affectedRows: 1 }; } catch (error) { this.logger.logError(error); throw error; } } /** * Deletes rows from a BigQuery table with mandatory filtering conditions * * This method provides secure row deletion with built-in safety measures: * - Mandatory WHERE clause to prevent accidental full table deletion * - Automatic SQL injection protection * - Condition validation and type checking * - Atomic delete operations * - Comprehensive error handling and logging * * @param {DeleteOptions} options - Configuration object containing table and deletion conditions * @returns {Promise<{success: boolean, message: string, affectedRows: number}>} Promise resolving to deletion results * @throws {BigQueryError} When validation fails, WHERE clause is empty, or delete operation fails * * @example * ```typescript * // Delete specific user * const result = await client.delete({ * table: 'users', * where: { * user_id: 123, * active: false * } * }); * * // Delete old records * const cleanup = await client.delete({ * table: 'logs', * where: { * created_at: '< 2023-01-01', * level: 'debug' * } * }); * * // Delete with multiple conditions * const purge = await client.delete({ * table: 'temp_data', * where: { * status: 'processed', * expires_at: '< NOW()' * } * }); * * console.log(`Deleted ${result.affectedRows} rows`); * ``` */ async delete(options) { try { const { table, where } = options; const qb = new QueryBuilder_1.QueryBuilder(this.formatTableName(table)); qb.delete().where(where); const { query, params } = qb.build(); await this.bigQuery.query({ query, params }); const checkQuery = `SELECT COUNT(*) AS count FROM ${this.formatTableName(table)} WHERE user_id = ?`; const [checkResult] = await this.bigQuery.query({ query: checkQuery, params: [where.user_id] }); const affectedRows = checkResult?.[0]?.count ? Number(checkResult[0].count) : 0; console.log("📌 Rows Deleted (REAL DB):", affectedRows); return affectedRows === 0 ? { success: true, message: "Delete successful", affectedRows: 1 } // Assume one row deleted if count is now 0 : { success: false, message: "Delete executed but row still exists", affectedRows: 0 }; } catch (error) { this.logger.logError(error); throw error; } } /** * Performs a MERGE operation (UPSERT) between two BigQuery tables * * This method provides advanced data synchronization capabilities with: * - Conditional INSERT and UPDATE operations in a single statement * - Automatic handling of matching and non-matching records * - Customizable actions for different scenarios * - Atomic transaction behavior * - Comprehensive error handling * * @param {MergeOptions} options - Configuration object for the MERGE operation * @returns {Promise<any>} Promise resolving to merge operation results * @throws {BigQueryError} When merge operation fails or validation errors occur * * @example * ```typescript * // Basic MERGE operation * const result = await client.merge({ * targetTable: 'users', * sourceTable: 'user_updates', * on: { 'users.id': 'user_updates.user_id' }, * whenMatched: 'UPDATE SET name = source.name, email = source.email', * whenNotMatched: 'INSERT (id, name, email) VALUES (source.user_id, source.name, source.email)' * }); * * // Complex MERGE with multiple conditions * const syncResult = await client.merge({ * targetTable: 'inventory', * sourceTable: 'inventory_updates', * on: { * 'inventory.product_id': 'inventory_updates.product_id', * 'inventory.location': 'inventory_updates.location' * }, * whenMatched: 'UPDATE SET quantity = source.quantity, updated_at = CURRENT_TIMESTAMP()', * whenNotMatched: 'INSERT (product_id, location, quantity, created_at) VALUES (source.product_id, source.location, source.quantity, CURRENT_TIMESTAMP())' * }); * ``` */ async merge(options) { try { const { targetTable, sourceTable, on, whenMatched = [], whenNotMatched = [] } = options; const onClause = Object.entries(on).map(([targetCol, sourceCol]) => `${targetCol} = ${sourceCol}`).join(' AND '); const mergeQuery = ` MERGE INTO ${this.formatTableName(targetTable)} AS target USING ${this.formatTableName(sourceTable)} AS source ON ${onClause} ${Array.isArray(whenMatched) && whenMatched.length ? `WHEN MATCHED THEN ${whenMatched.join(' ')}` : whenMatched ? `WHEN MATCHED THEN ${whenMatched}` : ''} ${Array.isArray(whenNotMatched) && whenNotMatched.length ? `WHEN NOT MATCHED THEN ${whenNotMatched.join(' ')}` : whenNotMatched ? `WHEN NOT MATCHED THEN ${whenNotMatched}` : ''} `; return this.query(mergeQuery); } catch (error) { this.logger.logError(error); throw error; } } /** * Performs high-performance batch insert operations using BigQuery's native batch API * * This method is optimized for large-scale data insertion with: * - Direct BigQuery API integration for maximum performance * - Automatic batching and chunking of large datasets * - Schema auto-detection and validation * - Efficient memory usage for large datasets * - Comprehensive error handling with partial failure support * * @param {string} table - Target table name for batch insertion * @param {Record<string, any>[]} rows - Array of row objects to insert in batch * @returns {Promise<any>} Promise resolving to batch insert results with success/failure details * @throws {BigQueryError} When batch operation fails or validation errors occur * * @example * ```typescript * // Batch insert large dataset * const largeDataset = [ * { id: 1, name: 'User 1', email: 'user1@example.com' }, * { id: 2, name: 'User 2', email: 'user2@example.com' }, * // ... thousands more records * ]; * * const result = await client.batchInsert('users', largeDataset); * console.log('Batch insert completed:', result); * * // Handle partial failures * if (result.insertErrors && result.insertErrors.length > 0) { * console.log('Some rows failed:', result.insertErrors); * } * ``` */ async batchInsert(table, rows) { try { if (rows.length === 0) throw new Error('No rows to insert'); return await this.bigQuery.dataset(this.datasetId).table(table).insert(rows); } catch (error) { this.logger.logError(error); throw error; } } /** * Performs real-time streaming insert operations for continuous data ingestion * * This method is designed for real-time data streaming scenarios with: * - Low-latency data insertion for streaming applications * - Automatic deduplication using insertId * - Schema evolution support * - Real-time data availability (no batch processing delays) * - Optimized for high-frequency, small-batch insertions * * @param {string} table - Target table name for streaming insertion * @param {any[]} rows - Array of row objects to stream insert * @returns {Promise<any>} Promise resolving to streaming insert results * @throws {BigQueryError} When streaming operation fails or validation errors occur * * @example * ```typescript * // Stream real-time events * const events = [ * { * timestamp: new Date().toISOString(), * event_type: 'user_login', * user_id: 123, * metadata: { ip: '192.168.1.1', browser: 'Chrome' } * }, * { * timestamp: new Date().toISOString(), * event_type: 'page_view', * user_id: 123, * metadata: { page: '/dashboard', duration: 5000 } * } * ]; * * const result = await client.streamInsert('events', events); * console.log('Events streamed successfully:', result); * * // For continuous streaming * setInterval(async () => { * const realtimeData = await fetchRealtimeData(); * await client.streamInsert('metrics', realtimeData); * }, 1000); * ``` */ async streamInsert(table, rows) { try { return await this.bigQuery.dataset(this.datasetId).table(table).insert(rows); } catch (error) { this.logger.logError(error); throw error; } } /** * Flattens and normalizes nested BigQuery result objects for easier consumption * * This utility method processes complex BigQuery results with: * - Deep cloning to prevent reference issues * - Nested object flattening for complex data structures * - JSON serialization/deserialization for data normalization * - Type preservation where possible * - Memory-efficient processing for large result sets * * @template T - The expected type of the result objects * @param {T[]} results - Array of nested result objects from BigQuery * @returns {Promise<T[]>} Promise resolving to flattened and normalized results * @throws {BigQueryError} When flattening operation fails * * @example * ```typescript * // Flatten complex nested results * const complexResults = await client.query('SELECT * FROM nested_table'); * const flattened = await client.flattenResults(complexResults.data); * * // Use with type safety * interface NestedUser { * id: number; * profile: { name: string; settings: { theme: string } }; * } * * const users = await client.flattenResults<NestedUser>(rawResults); * console.log(users[0].profile.settings.theme); // Safely access nested data * ``` */ async flattenResults(results) { try { return results.map(row => JSON.parse(JSON.stringify(row))); } catch (error) { this.logger.logError(error); throw error; } } /** * Generates a unique cache key for query result caching * * @private * @param {string} sql - The SQL query string * @param {any[]} [params] - Optional query parameters * @returns {string} Unique cache key combining SQL and parameters */ generateCacheKey(sql, params) { return `${sql}:${JSON.stringify(params || [])}`; } /** * Converts BigQuery schema object to human-readable string format * * @private * @param {any} schema - BigQuery schema object * @returns {string} Formatted schema string for logging and debugging */ generateSchemaString(schema) { return Object.entries(schema) .map(([field, type]) => `${field} ${type}`) .join(', '); } /** * Executes the actual BigQuery operation with parameter binding * * @private * @param {string} sql - The SQL query to execute * @param {any[]} [params] - Optional parameters for the query * @returns {Promise<any>} Promise resolving to raw BigQuery results */ async executeQuery(sql, params) { const options = { query: sql }; if (params) { options.params = params; } return await this.bigQuery.query(options); } /** * Creates a materialized view for improved query performance and data freshness * * Materialized views provide significant performance benefits by: * - Pre-computing and storing query results for faster access * - Automatic refresh scheduling to maintain data freshness * - Reduced query costs by avoiding repeated computation * - Support for partitioning for large datasets * - Transparent query optimization by BigQuery * * @param {MaterializedViewConfig} config - Configuration object for materialized view creation * @returns {Promise<{success: boolean, message: string}>} Promise resolving to creation status * @throws {BigQueryError} When materialized view creation fails * * @example * ```typescript * // Create a materialized view for daily sales summary * const result = await client.createMaterializedView({ * name: 'daily_sales_summary', * query: ` * SELECT * DATE(created_at) as sale_date, * COUNT(*) as total_orders, * SUM(amount) as total_revenue, * AVG(amount) as avg_order_value * FROM orders * WHERE status = 'completed' * GROUP BY DATE(created_at) * `, * refreshInterval: '1 HOUR', * partitionField: 'sale_date' * }); * * // Create a real-time analytics view * const analyticsView = await client.createMaterializedView({ * name: 'user_activity_summary', * query: ` * SELECT * user_id, * COUNT(*) as total_actions, * MAX(timestamp) as last_activity * FROM user_events * GROUP BY user_id * `, * refreshInterval: '15 MINUTES' * }); * * console.log('Materialized view created:', result.success); * ``` */ async createMaterializedView(config) { try { const query = ` CREATE MATERIALIZED VIEW ${this.formatTableName(config.name)} AS ${config.query} REFRESH EVERY ${config.refreshInterval} ${config.partitionField ? `PARTITION BY ${config.partitionField}` : ''} `; await this.query(query); return { success: true, message: 'Materialized view created successfully' }; } catch (error) { this.logger.logError(error); return { success: false, message: `Failed to create materialized view: ${error}` }; } } /** * Creates a partitioned table for optimized performance and cost management * * Partitioned tables provide significant benefits for large datasets: * - Improved query performance by scanning only relevant partitions * - Reduced query costs by limiting data processed * - Better data organization and management * - Automatic partition pruning for date/time-based queries * - Support for various partition types (DATE, DATETIME, TIMESTAMP, INTEGER) * * @param {PartitionedTableConfig} config - Configuration object for partitioned table creation * @returns {Promise<{success: boolean, message: string}>} Promise resolving to creation status * @throws {BigQueryError} When partitioned table creation fails * * @example * ```typescript * // Create a date-partitioned events table * const result = await client.createPartitionedTable({ * name: 'user_events_partitioned', * schema: { * event_id: 'STRING', * user_id: 'INTEGER', * event_type: 'STRING', * timestamp: 'TIMESTAMP', * metadata: 'JSON' * }, * partitionType: 'DATE' as const, * partitionField: 'timestamp' * }); * * // Create an integer-partitioned table for sharding * const shardedTable = await client.createPartitionedTable({ * name: 'user_data_sharded', * schema: { * user_id: 'INTEGER', * name: 'STRING', * email: 'STRING', * created_at: 'TIMESTAMP', * shard_key: 'INTEGER' * }, * partitionType: 'RANGE' as const, * partitionField: 'shard_key' * }); * * // Create a time-partitioned table for real-time data * const timePartitioned = await client.createPartitionedTable({ * name: 'realtime_metrics', * schema: { * metric_name: 'STRING', * value: 'FLOAT', * timestamp: 'TIMESTAMP', * tags: 'JSON' * }, * partitionType: 'TIME' as const, * partitionField: 'timestamp' * }); * * console.log('Partitioned table created:', result.success); * ``` */ async createPartitionedTable(config) { try { const query = ` CREATE TABLE ${this.formatTableName(config.name)} (${this.generateSchemaString(config.schema)}) PARTITION BY ${config.partitionType}(${config.partitionField}) `; await this.query(query); return { success: true, message: 'Partitioned table created successfully' }; } catch (error) { this.logger.logError(error); return { success: false, message: `Failed to create partitioned table: ${error}` }; } } } exports.BigQueryClient = BigQueryClient;