diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 000000000..078c0e18c --- /dev/null +++ b/examples/README.md @@ -0,0 +1,66 @@ +# GaussDB Node.js Examples + +这里包含了 gaussdb-node 的各种使用示例。 + +## 文件夹结构 + +### `async-await/` - 现代异步语法示例 +使用 async/await 语法的示例。 + +### `promises/` - 传统 Promise 语法示例 +使用 Promise 链式语法的示例。 + +## 快速开始 + +安装 gaussdb-node +```bash +npm install gaussdb-node +``` + +## 环境变量配置 + +大部分示例会使用环境变量来配置数据库连接: + +```bash +export GAUSSUSER=user +export GAUSSPASSWORD=openGauss@123 +export GAUSSHOST=localhost +export GAUSSPORT=5432 +export GAUSSDATABASE=data +export GAUSSTESTNOSSL=false +``` + +## 示例类型 + +### 基础连接 +- 最简单的连接和查询示例 +- 使用配置对象连接数据库 + +### 连接池 +- 基础连接池使用 +- 连接池错误处理 +- 手动检出和释放连接 + +### 查询操作 +- 参数化查询防止SQL注入 +- 预编译语句使用 +- 事务处理 + +### 高级功能 +- 游标查询大结果集 +- 流式查询处理 + +## 运行示例 + +```bash +# 运行 async/await 示例 +cd async-await +node basic-connection.js + +# 运行 Promise 示例 +cd promises +node basic-connection.js + +# 使用环境变量运行 +GAUSSUSER=user GAUSSPASSWORD=openGauss@123 GAUSSHOST=localhost node basic-connection.js +``` diff --git a/examples/async-await/basic-connection.js b/examples/async-await/basic-connection.js new file mode 100644 index 000000000..d64b8f490 --- /dev/null +++ b/examples/async-await/basic-connection.js @@ -0,0 +1,20 @@ +import { Client } from 'gaussdb-node' + +const client = new Client() + +async function main() { + try { + await client.connect() + console.log('✓ 连接成功') + + const res = await client.query('SELECT $1::text as message', ['Hello world!']) + console.log(res.rows[0].message) // Hello world! + } catch (err) { + console.error('✗ 错误:', err.message) + } finally { + await client.end() + console.log('✓ 连接已关闭') + } +} + +main() diff --git a/examples/async-await/connection-pool.js b/examples/async-await/connection-pool.js new file mode 100644 index 000000000..4a6d0fbc1 --- /dev/null +++ b/examples/async-await/connection-pool.js @@ -0,0 +1,55 @@ +import { Pool } from 'gaussdb-node' + +const pool = new Pool({ + user: process.env.GAUSSUSER || 'user', + host: process.env.GAUSSHOST || 'localhost', + database: process.env.GAUSSDATABASE || 'data', + password: process.env.GAUSSPASSWORD || 'openGauss@123', + port: parseInt(process.env.GAUSSPORT) || 5432, + max: 10, + idleTimeoutMillis: 30000, +}) + +pool.on('error', (err) => { + console.error('✗ 连接池意外错误:', err) + process.exit(-1) +}) + +async function useConnectionPool() { + try { + // 创建临时表 + await pool.query(` + CREATE TEMP TABLE IF NOT EXISTS temp_users ( + id INTEGER, + name VARCHAR(100), + email VARCHAR(100), + created_at TIMESTAMP DEFAULT NOW() + ) + `) + console.log('✓ 临时表创建成功') + + // 插入测试数据 + await pool.query(` + INSERT INTO temp_users (id, name, email) VALUES + (1, '张三', 'zhangsan@example.com'), + (2, '李四', 'lisi@example.com'), + (3, '王五', 'wangwu@example.com') + `) + console.log('✓ 测试数据插入成功') + + // 查询数据 + const userRes = await pool.query('SELECT COUNT(*) as user_count FROM temp_users') + console.log('✓ 临时表用户数量:', userRes.rows[0].user_count) + + // 查询当前时间 + const timeRes = await pool.query('SELECT NOW() as current_time') + console.log('✓ 当前时间:', timeRes.rows[0].current_time) + } catch (err) { + console.error('✗ 查询失败:', err.message) + } finally { + await pool.end() + console.log('✓ 连接池已关闭') + } +} + +useConnectionPool() diff --git a/examples/async-await/connection-with-config.js b/examples/async-await/connection-with-config.js new file mode 100644 index 000000000..ec7a7143e --- /dev/null +++ b/examples/async-await/connection-with-config.js @@ -0,0 +1,25 @@ +import { Client } from 'gaussdb-node' + +const client = new Client({ + user: process.env.GAUSSUSER || 'user', + host: process.env.GAUSSHOST || 'localhost', + database: process.env.GAUSSDATABASE || 'data', + password: process.env.GAUSSPASSWORD || 'openGauss@123', + port: parseInt(process.env.GAUSSPORT) || 5432, +}) + +async function connectWithConfig() { + try { + await client.connect() + console.log('✓ 连接成功!') + + const res = await client.query('SELECT NOW() as current_time') + console.log('当前时间:', res.rows[0].current_time) + } catch (err) { + console.error('✗ 连接失败:', err.message) + } finally { + await client.end() + } +} + +connectWithConfig() diff --git a/examples/async-await/cursor-queries.js b/examples/async-await/cursor-queries.js new file mode 100644 index 000000000..671b744a9 --- /dev/null +++ b/examples/async-await/cursor-queries.js @@ -0,0 +1,63 @@ +import { Client } from 'gaussdb-node' +import Cursor from 'gaussdb-cursor' + +const client = new Client() + +async function demonstrateCursorQueries() { + try { + await client.connect() + console.log('创建临时表用于演示...') + + // 创建临时表 + await client.query(` + CREATE TEMP TABLE large_table ( + id INTEGER PRIMARY KEY, + name VARCHAR(100), + email VARCHAR(100), + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ) + `) + console.log('✓ 临时表创建成功') + + // 插入测试数据 + console.log('插入测试数据...') + for (let i = 1; i <= 1000; i++) { + await client.query('INSERT INTO large_table (id, name, email) VALUES ($1, $2, $3)', [ + i, + `用户${i}`, + `user${i}@example.com`, + ]) + } + console.log('✓ 插入了 1000 条测试数据') + + console.log('创建游标查询大数据集...') + const query = 'SELECT * FROM large_table ORDER BY id' + const cursor = client.query(new Cursor(query)) + + let totalRows = 0 + let batch + + // 批量读取数据,每次100行 + while ((batch = await cursor.read(100)).length > 0) { + totalRows += batch.length + console.log(`✓ 处理了 ${batch.length} 行,总计 ${totalRows} 行`) + + // 处理每一行数据 + batch.forEach((row) => { + // 处理每行数据,显示前5行作为示例 + if (row.id <= 5) { + console.log(` 行 ${row.id}: ${row.name} - ${row.email}`) + } + }) + } + + await cursor.close() + console.log(`✓ 游标查询完成,总共处理 ${totalRows} 行`) + } catch (err) { + console.error('✗ 游标查询失败:', err.message) + } finally { + await client.end() + } +} + +demonstrateCursorQueries() diff --git a/examples/async-await/error-handling.js b/examples/async-await/error-handling.js new file mode 100644 index 000000000..f915cd1a0 --- /dev/null +++ b/examples/async-await/error-handling.js @@ -0,0 +1,30 @@ +import { Client } from 'gaussdb-node' + +const client = new Client() + +async function demonstrateErrorHandling() { + try { + await client.connect() + + // 执行查询 + const res = await client.query('SELECT $1::text as message', ['Hello world!']) + console.log(res.rows[0].message) + } catch (err) { + // 处理连接或查询错误 + if (err.code === 'ECONNREFUSED') { + console.error('✗ 无法连接到数据库服务器') + } else if (err.code === '42P01') { + console.error('✗ 表不存在') + } else if (err.code === '28P01') { + console.error('✗ 认证失败') + } else { + console.error('✗ 数据库错误:', err.message) + } + } finally { + // 确保连接被正确关闭 + await client.end() + console.log('✓ 连接已关闭') + } +} + +demonstrateErrorHandling() diff --git a/examples/async-await/parameterized-queries.js b/examples/async-await/parameterized-queries.js new file mode 100644 index 000000000..3f7e23519 --- /dev/null +++ b/examples/async-await/parameterized-queries.js @@ -0,0 +1,43 @@ +import { Client } from 'gaussdb-node' + +const client = new Client() + +async function demonstrateParameterizedQueries() { + try { + await client.connect() + console.log('创建临时表用于演示...') + + // 创建临时表 + await client.query(` + CREATE TEMP TABLE users ( + id INTEGER PRIMARY KEY, + name VARCHAR(100), + email VARCHAR(100), + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ) + `) + console.log('✓ 临时表创建成功') + + // ✓ 安全的参数化查询 + const userId = 123 + const userName = "John O'Doe" + + const insertQuery = 'INSERT INTO users(id, name, email) VALUES($1, $2, $3) RETURNING *' + const insertValues = [userId, userName, 'john@example.com'] + + const insertResult = await client.query(insertQuery, insertValues) + console.log('✓ 插入用户:', insertResult.rows[0]) + + const selectQuery = 'SELECT * FROM users WHERE name = $1' + const selectValues = [userName] + + const selectResult = await client.query(selectQuery, selectValues) + console.log('✓ 查询结果:', selectResult.rows) + } catch (err) { + console.error('✗ 查询失败:', err.message) + } finally { + await client.end() + } +} + +demonstrateParameterizedQueries() diff --git a/examples/async-await/pool-checkout.js b/examples/async-await/pool-checkout.js new file mode 100644 index 000000000..f27a9832f --- /dev/null +++ b/examples/async-await/pool-checkout.js @@ -0,0 +1,54 @@ +import { Pool } from 'gaussdb-node' + +const pool = new Pool() + +async function demonstratePoolCheckout() { + // 手动检出连接 + const client = await pool.connect() + + try { + console.log('✓ 获取到连接') + console.log('创建临时表用于演示...') + + // 创建临时表 + await client.query(` + CREATE TEMP TABLE users ( + id INTEGER PRIMARY KEY, + name VARCHAR(100), + email VARCHAR(100), + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ) + `) + console.log('✓ 临时表创建成功') + + // 插入测试数据 + await client.query('INSERT INTO users (id, name, email) VALUES ($1, $2, $3)', [1, '张三', 'zhangsan@example.com']) + await client.query('INSERT INTO users (id, name, email) VALUES ($1, $2, $3)', [2, '李四', 'lisi@example.com']) + await client.query('INSERT INTO users (id, name, email) VALUES ($1, $2, $3)', [3, '王五', 'wangwu@example.com']) + console.log('✓ 插入了 3 条测试数据') + + // 开始事务 + await client.query('BEGIN') + + const res1 = await client.query('SELECT COUNT(*) FROM users') + console.log('用户总数:', res1.rows[0].count) + + const res2 = await client.query('SELECT NOW() as time') + console.log('查询时间:', res2.rows[0].time) + + // 提交事务 + await client.query('COMMIT') + console.log('✓ 事务提交成功') + } catch (err) { + console.error('✗ 查询失败:', err) + await client.query('ROLLBACK') + } finally { + // 释放连接回池中 + client.release() + console.log('✓ 连接已释放') + } + + await pool.end() +} + +demonstratePoolCheckout() diff --git a/examples/async-await/prepared-statements.js b/examples/async-await/prepared-statements.js new file mode 100644 index 000000000..4105719a3 --- /dev/null +++ b/examples/async-await/prepared-statements.js @@ -0,0 +1,47 @@ +import { Client } from 'gaussdb-node' + +const client = new Client() + +async function demonstratePreparedStatements() { + try { + await client.connect() + console.log('创建临时表用于演示...') + + // 创建临时表 + await client.query(` + CREATE TEMP TABLE users ( + id INTEGER PRIMARY KEY, + name VARCHAR(100), + email VARCHAR(100), + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ) + `) + console.log('✓ 临时表创建成功') + + // 插入测试数据 + await client.query('INSERT INTO users (id, name, email) VALUES ($1, $2, $3)', [1, '张三', 'zhangsan@example.com']) + await client.query('INSERT INTO users (id, name, email) VALUES ($1, $2, $3)', [2, '李四', 'lisi@example.com']) + await client.query('INSERT INTO users (id, name, email) VALUES ($1, $2, $3)', [3, '王五', 'wangwu@example.com']) + console.log('✓ 插入了 3 条测试数据') + + const queryConfig = { + name: 'fetch-user', + text: 'SELECT * FROM users WHERE id = $1', + values: [1], + } + + console.log('执行预编译语句...') + const res = await client.query(queryConfig) + console.log('✓ 用户信息:', res.rows[0]) + + queryConfig.values = [2] + const res2 = await client.query(queryConfig) + console.log('✓ 另一个用户:', res2.rows[0]) + } catch (err) { + console.error('✗ 查询失败:', err.message) + } finally { + await client.end() + } +} + +demonstratePreparedStatements() diff --git a/examples/async-await/streaming-queries.js b/examples/async-await/streaming-queries.js new file mode 100644 index 000000000..149ec00eb --- /dev/null +++ b/examples/async-await/streaming-queries.js @@ -0,0 +1,69 @@ +import { Client } from 'gaussdb-node' +import QueryStream from 'gaussdb-query-stream' + +const client = new Client() + +async function demonstrateStreamingQueries() { + try { + await client.connect() + console.log('创建临时表用于演示...') + + // 创建临时表 + await client.query(` + CREATE TEMP TABLE large_table ( + id INTEGER PRIMARY KEY, + name VARCHAR(100), + email VARCHAR(100), + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ) + `) + console.log('✓ 临时表创建成功') + + // 插入大量测试数据 + console.log('插入测试数据...') + for (let i = 1; i <= 500; i++) { + await client.query('INSERT INTO large_table (id, name, email) VALUES ($1, $2, $3)', [ + i, + `用户${i}`, + `user${i}@example.com`, + ]) + } + console.log('✓ 插入了 500 条测试数据') + + console.log('开始流式查询...') + const query = new QueryStream('SELECT * FROM large_table ORDER BY created_at') + const stream = client.query(query) + + let rowCount = 0 + + stream.on('data', (row) => { + rowCount++ + if (rowCount % 100 === 0 || rowCount <= 5) { + console.log(`处理第 ${rowCount} 行: ${row.name} (${row.email})`) + } + + // 处理每一行数据 + // 这里可以进行数据转换、验证等操作 + }) + + stream.on('end', () => { + console.log(`✓ 流式查询完成,总共处理 ${rowCount} 行`) + }) + + stream.on('error', (err) => { + console.error('✗ 流式查询错误:', err.message) + }) + + // 等待流结束 + await new Promise((resolve, reject) => { + stream.on('end', resolve) + stream.on('error', reject) + }) + } catch (err) { + console.error('✗ 查询失败:', err.message) + } finally { + await client.end() + } +} + +demonstrateStreamingQueries() diff --git a/examples/async-await/transaction-demo.js b/examples/async-await/transaction-demo.js new file mode 100644 index 000000000..5afa52a89 --- /dev/null +++ b/examples/async-await/transaction-demo.js @@ -0,0 +1,131 @@ +import { Client } from 'gaussdb-node' + +const client = new Client() + +async function demonstrateTransactionManagement() { + console.log('事务管理演示') + console.log('===========================') + + try { + await client.connect() + console.log('已连接到 GaussDB') + + await setupTestTables() + await basicTransactionExample() + await rollbackExample() + await savepointExample() + + console.log('\n事务示例完成!') + } catch (err) { + console.error('演示失败:', err.message) + } finally { + await client.end() + } +} + +async function setupTestTables() { + console.log('\n设置测试表...') + + await client.query(` + CREATE TEMP TABLE accounts ( + id INTEGER PRIMARY KEY, + name VARCHAR(100) NOT NULL, + balance NUMERIC(10,2) NOT NULL DEFAULT 0.00 + ) + `) + + await client.query('INSERT INTO accounts (id, name, balance) VALUES ($1, $2, $3)', [1, 'Alice', 1000]) + await client.query('INSERT INTO accounts (id, name, balance) VALUES ($1, $2, $3)', [2, 'Bob', 500]) + + console.log(' 测试表已创建') +} + +async function showBalances(title) { + console.log(` ${title}:`) + const result = await client.query('SELECT name, balance FROM accounts ORDER BY name') + for (const row of result.rows) { + console.log(` ${row.name}: $${parseFloat(row.balance).toFixed(2)}`) + } +} + +async function basicTransactionExample() { + console.log('\n1. 基础事务演示') + console.log('-------------------------') + await showBalances('转账前') + + try { + await client.query('BEGIN') + + const transferAmount = 150.0 + + await client.query('UPDATE accounts SET balance = balance - $1 WHERE name = $2', [transferAmount, 'Alice']) + await client.query('UPDATE accounts SET balance = balance + $1 WHERE name = $2', [transferAmount, 'Bob']) + + await client.query('COMMIT') + console.log(' 转账完成') + await showBalances('转账后') + } catch (err) { + await client.query('ROLLBACK') + console.error(' 转账失败:', err.message) + } +} + +async function rollbackExample() { + console.log('\n2. 回滚演示') + console.log('----------------') + await showBalances('回滚测试前') + + try { + await client.query('BEGIN') + + const invalidAmount = 2000.0 + console.log(` 尝试转账 $${invalidAmount} (超出余额)`) + + await client.query('UPDATE accounts SET balance = balance - $1 WHERE name = $2', [invalidAmount, 'Bob']) + + const result = await client.query('SELECT balance FROM accounts WHERE name = $1', ['Bob']) + if (parseFloat(result.rows[0].balance) < 0) { + throw new Error('余额不足') + } + + await client.query('COMMIT') + } catch (err) { + await client.query('ROLLBACK') + console.log(' 由于余额不足,事务已回滚') + await showBalances('回滚后 (未更改)') + } +} + +async function savepointExample() { + console.log('\n3. 保存点演示') + console.log('-----------------') + await showBalances('保存点测试前') + + try { + await client.query('BEGIN') + + await client.query('UPDATE accounts SET balance = balance - $1 WHERE name = $2', [50, 'Alice']) + await client.query('UPDATE accounts SET balance = balance + $1 WHERE name = $2', [50, 'Bob']) + console.log(' 步骤1: Alice -> Bob $50') + + await client.query('SAVEPOINT step1') + console.log(' 保存点已创建') + + await client.query('UPDATE accounts SET balance = balance - $1 WHERE name = $2', [100, 'Bob']) + await client.query('UPDATE accounts SET balance = balance + $1 WHERE name = $2', [100, 'Alice']) + console.log(' 步骤2: Bob -> Alice $100') + + console.log(' 模拟错误,回滚到保存点...') + await client.query('ROLLBACK TO SAVEPOINT step1') + console.log(' 已回滚到保存点 (步骤2已撤销,步骤1保留)') + + await client.query('COMMIT') + console.log(' 事务已提交') + await showBalances('保存点测试后') + } catch (err) { + await client.query('ROLLBACK') + console.error(' 保存点演示失败:', err.message) + } +} + +demonstrateTransactionManagement() diff --git a/examples/package.json b/examples/package.json new file mode 100644 index 000000000..ae25858bd --- /dev/null +++ b/examples/package.json @@ -0,0 +1,11 @@ +{ + "name": "gaussdb-node-examples", + "type": "module", + "description": "Examples for gaussdb-node", + "private": true, + "dependencies": { + "gaussdb-node": "workspace:*", + "gaussdb-cursor": "workspace:*", + "gaussdb-query-stream": "workspace:*" + } +} diff --git a/examples/promises/basic-connection.js b/examples/promises/basic-connection.js new file mode 100644 index 000000000..e18be6f4c --- /dev/null +++ b/examples/promises/basic-connection.js @@ -0,0 +1,25 @@ +import { Client } from 'gaussdb-node' + +const client = new Client() + +function main() { + client + .connect() + .then(() => { + console.log('✓ 连接成功') + return client.query('SELECT $1::text as message', ['Hello world!']) + }) + .then((res) => { + console.log(res.rows[0].message) // Hello world! + }) + .catch((err) => { + console.error('✗ 错误:', err.message) + }) + .finally(() => { + return client.end().then(() => { + console.log('✓ 连接已关闭') + }) + }) +} + +main() diff --git a/examples/promises/connection-pool.js b/examples/promises/connection-pool.js new file mode 100644 index 000000000..ce3f72d07 --- /dev/null +++ b/examples/promises/connection-pool.js @@ -0,0 +1,64 @@ +import { Pool } from 'gaussdb-node' + +const pool = new Pool({ + user: process.env.GAUSSUSER || 'user', + host: process.env.GAUSSHOST || 'localhost', + database: process.env.GAUSSDATABASE || 'data', + password: process.env.GAUSSPASSWORD || 'openGauss@123', + port: parseInt(process.env.GAUSSPORT) || 5432, + max: 10, + idleTimeoutMillis: 30000, +}) + +pool.on('error', (err) => { + console.error('✗ 连接池意外错误:', err) + process.exit(-1) +}) + +function useConnectionPool() { + // 创建临时表 + pool + .query( + ` + CREATE TEMP TABLE IF NOT EXISTS temp_users ( + id INTEGER, + name VARCHAR(100), + email VARCHAR(100), + created_at TIMESTAMP DEFAULT NOW() + ) + ` + ) + .then(() => { + console.log('✓ 临时表创建成功') + // 插入测试数据 + return pool.query(` + INSERT INTO temp_users (id, name, email) VALUES + (1, '张三', 'zhangsan@example.com'), + (2, '李四', 'lisi@example.com'), + (3, '王五', 'wangwu@example.com') + `) + }) + .then(() => { + console.log('✓ 测试数据插入成功') + // 查询数据 + return pool.query('SELECT COUNT(*) as user_count FROM temp_users') + }) + .then((userRes) => { + console.log('✓ 临时表用户数量:', userRes.rows[0].user_count) + // 查询当前时间 + return pool.query('SELECT NOW() as current_time') + }) + .then((timeRes) => { + console.log('✓ 当前时间:', timeRes.rows[0].current_time) + }) + .catch((err) => { + console.error('✗ 查询失败:', err.message) + }) + .finally(() => { + return pool.end().then(() => { + console.log('✓ 连接池已关闭') + }) + }) +} + +useConnectionPool() diff --git a/examples/promises/connection-with-config.js b/examples/promises/connection-with-config.js new file mode 100644 index 000000000..3e3afd3a0 --- /dev/null +++ b/examples/promises/connection-with-config.js @@ -0,0 +1,30 @@ +import { Client } from 'gaussdb-node' + +// 使用配置对象连接数据库 +const client = new Client({ + user: process.env.GAUSSUSER || 'user', + host: process.env.GAUSSHOST || 'localhost', + database: process.env.GAUSSDATABASE || 'data', + password: process.env.GAUSSPASSWORD || 'openGauss@123', + port: parseInt(process.env.GAUSSPORT) || 5432, +}) + +function connectWithConfig() { + client + .connect() + .then(() => { + console.log('✓ 连接成功!') + return client.query('SELECT NOW() as current_time') + }) + .then((res) => { + console.log('当前时间:', res.rows[0].current_time) + }) + .catch((err) => { + console.error('✗ 连接失败:', err.message) + }) + .finally(() => { + return client.end() + }) +} + +connectWithConfig() diff --git a/examples/promises/cursor-queries.js b/examples/promises/cursor-queries.js new file mode 100644 index 000000000..cd4a63a99 --- /dev/null +++ b/examples/promises/cursor-queries.js @@ -0,0 +1,80 @@ +import { Client } from 'gaussdb-node' +import Cursor from 'gaussdb-cursor' + +const client = new Client() + +function demonstrateCursorQueries() { + client + .connect() + .then(() => { + console.log('创建临时表用于演示...') + // 创建临时表 + return client.query(` + CREATE TEMP TABLE large_table ( + id INTEGER PRIMARY KEY, + name VARCHAR(100), + email VARCHAR(100), + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ) + `) + }) + .then(() => { + console.log('✓ 临时表创建成功') + console.log('插入测试数据...') + + // 插入测试数据 - 使用递归Promise来模拟循环 + function insertData(i) { + if (i > 1000) { + return Promise.resolve() + } + return client + .query('INSERT INTO large_table (id, name, email) VALUES ($1, $2, $3)', [i, `用户${i}`, `user${i}@example.com`]) + .then(() => insertData(i + 1)) + } + + return insertData(1) + }) + .then(() => { + console.log('✓ 插入了 1000 条测试数据') + console.log('创建游标查询大数据集...') + + const query = 'SELECT * FROM large_table ORDER BY id' + const cursor = client.query(new Cursor(query)) + + let totalRows = 0 + + // 批量读取数据的递归函数 + function readBatch() { + return cursor.read(100).then((batch) => { + if (batch.length === 0) { + return cursor.close().then(() => { + console.log(`✓ 游标查询完成,总共处理 ${totalRows} 行`) + }) + } + + totalRows += batch.length + console.log(`✓ 处理了 ${batch.length} 行,总计 ${totalRows} 行`) + + // 处理每一行数据 + batch.forEach((row) => { + // 处理每行数据,显示前5行作为示例 + if (row.id <= 5) { + console.log(` 行 ${row.id}: ${row.name} - ${row.email}`) + } + }) + + return readBatch() + }) + } + + return readBatch() + }) + .catch((err) => { + console.error('✗ 游标查询失败:', err.message) + }) + .finally(() => { + return client.end() + }) +} + +demonstrateCursorQueries() diff --git a/examples/promises/error-handling.js b/examples/promises/error-handling.js new file mode 100644 index 000000000..4ac3286ac --- /dev/null +++ b/examples/promises/error-handling.js @@ -0,0 +1,35 @@ +import { Client } from 'gaussdb-node' + +const client = new Client() + +function demonstrateErrorHandling() { + client + .connect() + .then(() => { + // 执行查询 + return client.query('SELECT $1::text as message', ['Hello world!']) + }) + .then((res) => { + console.log(res.rows[0].message) + }) + .catch((err) => { + // 处理连接或查询错误 + if (err.code === 'ECONNREFUSED') { + console.error('✗ 无法连接到数据库服务器') + } else if (err.code === '42P01') { + console.error('✗ 表不存在') + } else if (err.code === '28P01') { + console.error('✗ 认证失败') + } else { + console.error('✗ 数据库错误:', err.message) + } + }) + .finally(() => { + // 确保连接被正确关闭 + return client.end().then(() => { + console.log('✓ 连接已关闭') + }) + }) +} + +demonstrateErrorHandling() diff --git a/examples/promises/parameterized-queries.js b/examples/promises/parameterized-queries.js new file mode 100644 index 000000000..dc7aa8238 --- /dev/null +++ b/examples/promises/parameterized-queries.js @@ -0,0 +1,55 @@ +import { Client } from 'gaussdb-node' + +const client = new Client() + +function demonstrateParameterizedQueries() { + client + .connect() + .then(() => { + console.log('创建临时表用于演示...') + // 创建临时表 + return client.query(` + CREATE TEMP TABLE users ( + id INTEGER PRIMARY KEY, + name VARCHAR(100), + email VARCHAR(100), + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ) + `) + }) + .then(() => { + console.log('✓ 临时表创建成功') + + // ✓ 安全的参数化查询 + const userId = 123 + const userName = "John O'Doe" + + const insertQuery = 'INSERT INTO users(id, name, email) VALUES($1, $2, $3) RETURNING *' + const insertValues = [userId, userName, 'john@example.com'] + + return client.query(insertQuery, insertValues) + }) + .then((insertResult) => { + console.log('✓ 插入用户:', insertResult.rows[0]) + + const userName = "John O'Doe" + const selectQuery = 'SELECT * FROM users WHERE name = $1' + const selectValues = [userName] + + return client.query(selectQuery, selectValues) + }) + .then((selectResult) => { + console.log('✓ 查询结果:', selectResult.rows) + + // ✗ 危险:不要这样做(SQL注入风险) + // const badQuery = `SELECT * FROM users WHERE name = '${userName}'` + }) + .catch((err) => { + console.error('✗ 查询失败:', err.message) + }) + .finally(() => { + return client.end() + }) +} + +demonstrateParameterizedQueries() diff --git a/examples/promises/pool-checkout.js b/examples/promises/pool-checkout.js new file mode 100644 index 000000000..9513a1f63 --- /dev/null +++ b/examples/promises/pool-checkout.js @@ -0,0 +1,84 @@ +import { Pool } from 'gaussdb-node' + +const pool = new Pool() + +function demonstratePoolCheckout() { + pool + .connect() + .then((client) => { + console.log('✓ 获取到连接') + console.log('创建临时表用于演示...') + + // 创建临时表 + return client + .query( + ` + CREATE TEMP TABLE users ( + id INTEGER PRIMARY KEY, + name VARCHAR(100), + email VARCHAR(100), + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ) + ` + ) + .then(() => { + console.log('✓ 临时表创建成功') + + // 插入测试数据 + return client.query('INSERT INTO users (id, name, email) VALUES ($1, $2, $3)', [ + 1, + '张三', + 'zhangsan@example.com', + ]) + }) + .then(() => { + return client.query('INSERT INTO users (id, name, email) VALUES ($1, $2, $3)', [ + 2, + '李四', + 'lisi@example.com', + ]) + }) + .then(() => { + return client.query('INSERT INTO users (id, name, email) VALUES ($1, $2, $3)', [ + 3, + '王五', + 'wangwu@example.com', + ]) + }) + .then(() => { + console.log('✓ 插入了 3 条测试数据') + + // 开始事务 + return client.query('BEGIN') + }) + .then(() => { + return client.query('SELECT COUNT(*) FROM users') + }) + .then((res1) => { + console.log('用户总数:', res1.rows[0].count) + return client.query('SELECT NOW() as time') + }) + .then((res2) => { + console.log('查询时间:', res2.rows[0].time) + // 提交事务 + return client.query('COMMIT') + }) + .then(() => { + console.log('✓ 事务提交成功') + }) + .catch((err) => { + console.error('✗ 查询失败:', err) + return client.query('ROLLBACK') + }) + .finally(() => { + // 释放连接回池中 + client.release() + console.log('✓ 连接已释放') + }) + }) + .then(() => { + return pool.end() + }) +} + +demonstratePoolCheckout() diff --git a/examples/promises/prepared-statements.js b/examples/promises/prepared-statements.js new file mode 100644 index 000000000..03c16d029 --- /dev/null +++ b/examples/promises/prepared-statements.js @@ -0,0 +1,70 @@ +import { Client } from 'gaussdb-node' + +const client = new Client() + +function demonstratePreparedStatements() { + client + .connect() + .then(() => { + console.log('创建临时表用于演示...') + // 创建临时表 + return client.query(` + CREATE TEMP TABLE users ( + id INTEGER PRIMARY KEY, + name VARCHAR(100), + email VARCHAR(100), + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ) + `) + }) + .then(() => { + console.log('✓ 临时表创建成功') + // 插入测试数据 + return client.query('INSERT INTO users (id, name, email) VALUES ($1, $2, $3)', [ + 1, + '张三', + 'zhangsan@example.com', + ]) + }) + .then(() => { + return client.query('INSERT INTO users (id, name, email) VALUES ($1, $2, $3)', [2, '李四', 'lisi@example.com']) + }) + .then(() => { + return client.query('INSERT INTO users (id, name, email) VALUES ($1, $2, $3)', [3, '王五', 'wangwu@example.com']) + }) + .then(() => { + console.log('✓ 插入了 3 条测试数据') + + // 预编译语句提高性能 + const queryConfig = { + name: 'fetch-user', + text: 'SELECT * FROM users WHERE id = $1', + values: [1], + } + + console.log('执行预编译语句...') + return client.query(queryConfig) + }) + .then((res) => { + console.log('✓ 用户信息:', res.rows[0]) + + // 再次使用相同的预编译语句 + const queryConfig = { + name: 'fetch-user', + text: 'SELECT * FROM users WHERE id = $1', + values: [2], + } + return client.query(queryConfig) + }) + .then((res2) => { + console.log('✓ 另一个用户:', res2.rows[0]) + }) + .catch((err) => { + console.error('✗ 查询失败:', err.message) + }) + .finally(() => { + return client.end() + }) +} + +demonstratePreparedStatements() diff --git a/examples/promises/streaming-queries.js b/examples/promises/streaming-queries.js new file mode 100644 index 000000000..3e162c2c1 --- /dev/null +++ b/examples/promises/streaming-queries.js @@ -0,0 +1,80 @@ +import { Client } from 'gaussdb-node' +import QueryStream from 'gaussdb-query-stream' + +const client = new Client() + +function demonstrateStreamingQueries() { + client + .connect() + .then(() => { + console.log('创建临时表用于演示...') + // 创建临时表 + return client.query(` + CREATE TEMP TABLE large_table ( + id INTEGER PRIMARY KEY, + name VARCHAR(100), + email VARCHAR(100), + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ) + `) + }) + .then(() => { + console.log('✓ 临时表创建成功') + console.log('插入测试数据...') + + // 插入测试数据 - 使用递归Promise来模拟循环 + function insertData(i) { + if (i > 500) { + return Promise.resolve() + } + return client + .query('INSERT INTO large_table (id, name, email) VALUES ($1, $2, $3)', [ + i, + `用户${i}`, + `user${i}@example.com`, + ]) + .then(() => insertData(i + 1)) + } + + return insertData(1) + }) + .then(() => { + console.log('✓ 插入了 500 条测试数据') + console.log('开始流式查询...') + + const query = new QueryStream('SELECT * FROM large_table ORDER BY created_at') + const stream = client.query(query) + + let rowCount = 0 + + return new Promise((resolve, reject) => { + stream.on('data', (row) => { + rowCount++ + if (rowCount % 100 === 0 || rowCount <= 5) { + console.log(`处理第 ${rowCount} 行: ${row.name} (${row.email})`) + } + + // 处理每一行数据 + // 这里可以进行数据转换、验证等操作 + }) + + stream.on('end', () => { + console.log(`✓ 流式查询完成,总共处理 ${rowCount} 行`) + resolve() + }) + + stream.on('error', (err) => { + console.error('✗ 流式查询错误:', err.message) + reject(err) + }) + }) + }) + .catch((err) => { + console.error('✗ 查询失败:', err.message) + }) + .finally(() => { + return client.end() + }) +} + +demonstrateStreamingQueries() diff --git a/examples/promises/transaction-demo.js b/examples/promises/transaction-demo.js new file mode 100644 index 000000000..b81f6059f --- /dev/null +++ b/examples/promises/transaction-demo.js @@ -0,0 +1,177 @@ +import { Client } from 'gaussdb-node' + +const client = new Client() + +function demonstrateTransactionManagement() { + console.log('事务管理演示') + console.log('===========================') + + return client + .connect() + .then(() => { + console.log('已连接到 GaussDB') + return setupTestTables() + }) + .then(() => basicTransactionExample()) + .then(() => rollbackExample()) + .then(() => savepointExample()) + .then(() => { + console.log('\n事务示例完成!') + }) + .catch((err) => { + console.error('演示失败:', err.message) + }) + .finally(() => { + return client.end() + }) +} + +function setupTestTables() { + console.log('\n设置测试表...') + + return client + .query( + ` + CREATE TEMP TABLE accounts ( + id INTEGER PRIMARY KEY, + name VARCHAR(100) NOT NULL, + balance NUMERIC(10,2) NOT NULL DEFAULT 0.00 + ) + ` + ) + .then(() => { + return client.query('INSERT INTO accounts (id, name, balance) VALUES ($1, $2, $3)', [1, 'Alice', 1000]) + }) + .then(() => { + return client.query('INSERT INTO accounts (id, name, balance) VALUES ($1, $2, $3)', [2, 'Bob', 500]) + }) + .then(() => { + console.log(' 测试表已创建') + }) +} + +function showBalances(title) { + console.log(` ${title}:`) + return client.query('SELECT name, balance FROM accounts ORDER BY name').then((result) => { + for (const row of result.rows) { + console.log(` ${row.name}: $${parseFloat(row.balance).toFixed(2)}`) + } + }) +} + +function basicTransactionExample() { + console.log('\n1. 基础事务演示') + console.log('-------------------------') + + return showBalances('转账前') + .then(() => { + return client.query('BEGIN') + }) + .then(() => { + const transferAmount = 150.0 + + return client + .query('UPDATE accounts SET balance = balance - $1 WHERE name = $2', [transferAmount, 'Alice']) + .then(() => { + return client.query('UPDATE accounts SET balance = balance + $1 WHERE name = $2', [transferAmount, 'Bob']) + }) + }) + .then(() => { + return client.query('COMMIT') + }) + .then(() => { + console.log(' 转账完成') + return showBalances('转账后') + }) + .catch((err) => { + return client.query('ROLLBACK').then(() => { + console.error(' 转账失败:', err.message) + }) + }) +} + +function rollbackExample() { + console.log('\n2. 回滚演示') + console.log('----------------') + + return showBalances('回滚测试前') + .then(() => { + return client.query('BEGIN') + }) + .then(() => { + const invalidAmount = 2000.0 + console.log(` 尝试转账 $${invalidAmount} (超出余额)`) + + return client + .query('UPDATE accounts SET balance = balance - $1 WHERE name = $2', [invalidAmount, 'Bob']) + .then(() => { + return client.query('SELECT balance FROM accounts WHERE name = $1', ['Bob']) + }) + .then((result) => { + if (parseFloat(result.rows[0].balance) < 0) { + throw new Error('余额不足') + } + return client.query('COMMIT') + }) + }) + .catch((err) => { + return client.query('ROLLBACK').then(() => { + console.log(' 由于余额不足,事务已回滚') + return showBalances('回滚后 (未更改)') + }) + }) +} + +function savepointExample() { + console.log('\n3. 保存点演示') + console.log('-----------------') + + return showBalances('保存点测试前') + .then(() => { + return client.query('BEGIN') + }) + .then(() => { + return client + .query('UPDATE accounts SET balance = balance - $1 WHERE name = $2', [50, 'Alice']) + .then(() => { + return client.query('UPDATE accounts SET balance = balance + $1 WHERE name = $2', [50, 'Bob']) + }) + .then(() => { + console.log(' 步骤1: Alice -> Bob $50') + }) + }) + .then(() => { + return client.query('SAVEPOINT step1').then(() => { + console.log(' 保存点已创建') + }) + }) + .then(() => { + return client + .query('UPDATE accounts SET balance = balance - $1 WHERE name = $2', [100, 'Bob']) + .then(() => { + return client.query('UPDATE accounts SET balance = balance + $1 WHERE name = $2', [100, 'Alice']) + }) + .then(() => { + console.log(' 步骤2: Bob -> Alice $100') + }) + }) + .then(() => { + console.log(' 模拟错误,回滚到保存点...') + return client.query('ROLLBACK TO SAVEPOINT step1').then(() => { + console.log(' 已回滚到保存点 (步骤2已撤销,步骤1保留)') + }) + }) + .then(() => { + return client.query('COMMIT').then(() => { + console.log(' 事务已提交') + return showBalances('保存点测试后') + }) + }) + .catch((err) => { + return client.query('ROLLBACK').then(() => { + console.error(' 保存点演示失败:', err.message) + }) + }) +} + +demonstrateTransactionManagement()