Releases: TimelordUK/node-sqlserver-v8
always on encryption
new promise to cancel a query from returned Query
it('use promise on query to cancel', async function handler () {
const conn = env.theConnection
const waitsql = env.waitForSql(20)
const q = conn.query(waitsql)
const err = await q.promises.cancel()
assert(err.message.includes('Operation canceled'))
})
new promise to call on a fetched procedure
await createProc(spName)
const msg = 'hello world'
const proc = await env.theConnection.promises.getProc(spName)
const res = await proc.promises.call({
param: msg
})
improved typings and JSDoc in index.d.ts - part 1 this will be continued in future releases, any contributions from TS typings experts very much welcome.
any changes must be compatible with current typescript test code
i.e. npm run build must compile
npm run build
> msnodesqlv8@3.0.0 build
> tsc
msnodesqlv8 git(master) node .\dist\samples\typescript\builder.js
IF OBJECT_ID('dbo.tmpTableBuilder', 'U') IS NOT NULL DROP TABLE dbo.tmpTableBuilder;
CREATE TABLE dbo.tmpTableBuilder ([id] int , [col_a] int NOT NULL, [col_b] varchar (100) NOT NULL, [col_c] int NOT NULL, [col_d] int NOT NULL, [col_e] varchar (100) NOT NULL)
[
{
"id": 0,
"col_a": 0,
"col_b": "str_0",
"col_c": 1,
"col_d": -1,
"col_e": "str2_0"
},
{
"id": 1,
"col_a": 5,
"col_b": "str_1",
"col_c": 2,
"col_d": 0,
"col_e": "str2_1"
},
{
"id": 2,
"col_a": 10,
"col_b": "str_2",
"col_c": 3,
"col_d": 1,
"col_e": "str2_2"
},
{
"id": 3,
"col_a": 15,
"col_b": "str_3",
"col_c": 4,
"col_d": 2,
"col_e": "str2_3"
},
{
"id": 4,
"col_a": 20,
"col_b": "str_4",
"col_c": 5,
"col_d": 3,
"col_e": "str2_4"
}
]
CREATE TYPE dbo.tmpTableBuilderType AS TABLE ([id] int , [col_a] int , [col_b] varchar (100) , [col_c] int , [col_d] int , [col_e] varchar (100) )
create or alter procedure dbo.tmpTableBuilder_tvp_inserter
(
@tvp dbo.tmpTableBuilderType READONLY
)
AS
BEGIN
set nocount on
INSERT INTO dbo.tmpTableBuilder
(
[id],
[col_a],
[col_b],
[col_c],
[col_d],
[col_e]
)
SELECT
[id],
[col_a],
[col_b],
[col_c],
[col_d],
[col_e]
n FROM @tvp tvp
END
id 0 equals true
id 1 equals true
id 2 equals true
id 3 equals true
id 4 equals true
done
always on encryption now working on stored proc and bound table calls - please see tests\encrypt.js
e.g with table below
CREATE TABLE node.dbo.test_encrpted_table (
[id] int IDENTITY(1,1) NOT NULL, [field] int ENCRYPTED WITH
(COLUMN_ENCRYPTION_KEY = [CEK_Auto1],
ENCRYPTION_TYPE = Deterministic,
ALGORITHM = 'AEAD_AES_256_CBC_HMAC_SHA_256'))
const table = await conn.promises.getTable('test_encrpted_table')
const v =
[
{
field: 12345,
},
]
await table.promises.insert(v)
electron v22 binaries included
v2.7.0
- Node 19 binaries.
- better handling of blocked long running queries #264
const timeoutSproc = `CREATE OR ALTER PROCEDURE <name>
AS
BEGIN
\tSET NOCOUNT ON;
\tSET XACT_ABORT ON;
\tWAITFOR DELAY '00:10'; -- 10 minutes
END;
`
it('pool: sproc with timeout early terminates - check pool', async function handler () {
const spName = 'timeoutTest'
const size = 4
const pool = env.pool(size)
await pool.promises.open()
await env.promisedCreate(spName, timeoutSproc)
try {
await pool.promises.callProc(spName, {}, { timeoutMs: 2000 })
throw new Error('expected exception')
} catch (err) {
assert(err)
assert(err.message.includes('Query cancelled'))
const res = await pool.promises.query('select 1 as n')
expect(res.first[0].n).equals(1)
} finally {
await pool.close()
}
})
v2.6.0
- #254 Segmentation Fault while attempting to use Table Value Paramaters
- #255 Bulk insert not possible for tables that contain timestamp
- leak when using promise query on prepared statements.
- Driver={ODBC Driver 18 for SQL Server} support on all platforms
- electron v19 binaries
- linting cpp reduce build warnings
- upload node 18 32 bit windows
- add windows electron v20, v21
v2.5.0
export interface AggregatorPromises {
query(sql: string, params?: any[], options?: QueryAggregatorOptions): Promise<QueryAggregatorResults>
callProc(name: string, params?: any, options?: QueryAggregatorOptions): Promise<QueryAggregatorResults>
}
export interface PoolPromises extends AggregatorPromises {
open(): Promise<Pool>
close(): Promise<any>
getUserTypeTable(name: string): Promise<Table>
getTable(name: string): Promise<BulkTableMgr>
getProc(name: string): Promise<ProcedureDefinition>
}
-
prepared statements with nvarchar(max) #248
-
prepared statement may cause node crash due to stack corruption.
-
use promise query to add missing column names
it('select promise query with no column name', async function handler () {
const r1 = await env.theConnection.promises.query('select 1,2 union select 3,4', [],
{ replaceEmptyColumnNames: true })
console.log(JSON.stringify(r1.first, null, 0))
expect(r1.first[0].Column0).is.equal(1)
expect(r1.first[0].Column1).is.equal(2)
expect(r1.first[1].Column0).is.equal(3)
expect(r1.first[1].Column1).is.equal(4)
})
- significant refactor of test framework using bdd mocha, chai
call proc from connection pool (like connection)
it('use pool to call proc', async function handler () {
const pool = env.pool()
await pool.open()
const spName = 'test_sp_get_optional_p'
const a = 10
const b = 20
const def = `alter PROCEDURE <name> (
@a INT = ${a},
@b INT = ${b},
@plus INT out
)
AS begin
-- SET XACT_ABORT ON;
SET NOCOUNT ON;
set @plus = @a + @b;
end;
`
await env.promisedCreate(spName, def)
const expected = [
0,
a + b
]
const o = {}
const res = await pool.promises.callProc(spName, o)
expect(res.output).to.be.deep.equal(expected)
await pool.close()
})
use bulk table manager on pool (like connection)
it('use tableMgr on pool bulk insert varchar vector - exactly 4000 chars', async function handler () {
const pool = env.pool(4)
await pool.promises.open()
const b = env.repeat('z', 4000)
const helper = env.typeTableHelper('NVARCHAR(MAX)', pool)
const expected = helper.getVec(10, i => b)
const table = await helper.create()
const promisedInsert = table.promises.insert
const promisedSelect = table.promises.select
await promisedInsert(expected)
const res = await promisedSelect(expected)
expect(res).to.be.deep.equals(expected)
await pool.close()
})
use tvp on pool (like connection)
it('use pool for tvp insert', async function handler () {
const pool = env.pool(4)
await pool.promises.open()
const tableName = 'TestTvp'
const helper = env.tvpHelper(tableName, pool)
const vec = helper.getExtendedVec(1)
const table = await helper.create(tableName)
table.addRowsFromObjects(vec)
const tp = env.sql.TvpFromTable(table)
const insertSql = helper.callProcWithTVpSql
// 'exec insertTestTvp @tvp = ?;'
await pool.promises.query(insertSql, [tp])
// use a connection having inserted with pool
const res = await pool.promises.query(`select * from ${tableName}`)
expect(res.first).to.be.deep.equal(vec)
await pool.close()
})
v2.4.8
- pool PR fix- thanks #244
- electron v18
- query that returns BigInt (and all numeric) as strings rather than numbers
test('query a bigint implicit - configure query to return as string', testDone => {
async function runner () {
const num = '9223372036854775807'
const q = `SELECT ${num} as number`
const res = await theConnection.promises.query({
query_str: q,
numeric_string: true
})
try {
assert.deepStrictEqual(res.first[0].number, num)
} catch (e) {
assert.ifError(e)
}
}
runner().then(() => {
testDone()
})
})
v2.4.7
import { SqlClient, TableBuilder, Connection, TableManager } from 'msnodesqlv8';
// require the module so it can be used in your node JS code.
export const sql : SqlClient = require('msnodesqlv8');
const path = require('path')
const { GetConnection } = require(path.join(__dirname, '..\\javascript\\', '../javascript/get-connection'))
const connectionString: string = new GetConnection().connectionString
async function builder () {
function makeOne (i: number): any {
return {
id: i,
col_a: i * 5,
col_b: `str_${i}`,
col_c: i + 1,
col_d: i - 1,
col_e: `str2_${i}`
}
}
try {
const rows = 5
const connection: Connection = await sql.promises.open(connectionString)
const tableName = 'tmpTableBuilder'
const mgr: TableManager = connection.tableMgr()
const builder: TableBuilder = mgr.makeBuilder(tableName)
builder.setDialect(mgr.ServerDialect.SqlServer)
builder.addColumn('id').asInt().isPrimaryKey(1)
builder.addColumn('col_a').asInt()
builder.addColumn('col_b').asVarChar(100)
builder.addColumn('col_c').asInt()
builder.addColumn('col_d').asInt()
builder.addColumn('col_e').asVarChar(100)
const vec = []
for (let i = 0; i < rows; ++i) {
vec.push(makeOne(i))
}
const t = builder.toTable()
const create: string = builder.createTableSql
const drop: string = builder.dropTableSql
console.log(drop)
await builder.drop()
console.log(create)
await builder.create()
await t.promises.insert(vec)
const keys: any[] = t.keys(vec)
const res: any[] = await t.promises.select(keys)
console.log(JSON.stringify(res, null, 4))
await builder.drop()
await connection.promises.close()
} catch (e) {
console.log(e)
}
}
async function run () {
await builder()
}
run().then(() => {
console.log('done')
})
v2.4.6
- manually register a stored procedure (See example below)
- Sybase Adaptive Sever support
const sql = require('msnodesqlv8')
const { GetConnection } = require('./get-connection')
const connectionString = new GetConnection().connectionString
const query = 'SELECT top 5 * FROM syscomments'
// "Driver={Adaptive Server Enterprise}; app=myAppName; server=localhost port=5000; db=pubs3; uid=sa; pwd=ooooo;"
function legacyQuery () {
return new Promise((resolve, reject) => {
sql.open(connectionString, (err, con) => {
if (err) {
reject(err)
}
con.query(query, (err, rows) => {
if (err) {
reject(err)
}
con.close(() => {
resolve(rows)
})
})
})
})
}
async function promised () {
const connection = await sql.promises.open(connectionString)
const res = await connection.promises.query(query)
console.log(`promised ${JSON.stringify(res, null, 4)}`)
await connection.promises.close()
return res
}
async function q1 () {
const d = new Date()
try {
const rows = await legacyQuery()
const elapsed = new Date() - d
console.log(`legacyQuery rows.length ${rows.length} elapsed ${elapsed}`)
console.log(`legacyQuery ${JSON.stringify(rows, null, 4)}`)
} catch (err) {
console.error(err)
}
}
async function runProcWith (connection, spName, p) {
console.log(`call proc ${spName} with params ${JSON.stringify(p, null, 4)}`)
const res = await connection.promises.callProc(spName, p)
const returns = res.first[0]['']
console.log(`proc with params returns ${returns}`)
}
async function makeProc (connection, spName) {
try {
const pm = connection.procedureMgr()
const def = `create or replace proc tmp_name_concat
@last_name varchar(30) = "knowles",
@first_name varchar(18) = "beyonce" as
select @first_name + " " + @last_name `
await connection.promises.query(def)
const params = [
pm.makeParam(spName, '@last_name', 'varchar', 30, false),
pm.makeParam(spName, '@first_name', 'varchar', 18, false)
]
const proc = pm.addProc(spName, params)
proc.setDialect(pm.ServerDialect.Sybase)
return proc
} catch (err) {
console.error(err)
}
}
async function proc () {
const connection = await sql.promises.open(connectionString)
const spName = 'tmp_name_concat'
await makeProc(connection, spName)
try {
await runProcWith(connection, spName, {
first_name: 'Baby'
})
await runProcWith(connection, spName, {})
await runProcWith(connection, spName, {
first_name: 'Miley',
last_name: 'Cyrus'
})
await connection.promises.close()
} catch (err) {
console.error(err)
}
}
async function runOutputProcWith (connection, spName, p) {
console.log(`call output proc ${spName} with params ${JSON.stringify(p, null, 4)}`)
const res = await connection.promises.callProc(spName, p)
console.log(`output proc with params returns ${JSON.stringify(res, null, 4)}`)
}
async function makeOutputProc (connection, spName) {
try {
const pm = connection.procedureMgr()
const def = `create or replace proc tmp_square
@num decimal,
@square decimal output as
select @square=@num* @num`
await connection.promises.query(def)
const params = [
pm.makeParam(spName, '@num', 'decimal', 17, false),
pm.makeParam(spName, '@square', 'decimal', 17, true)
]
const proc = pm.addProc(spName, params)
proc.setDialect(pm.ServerDialect.Sybase)
return proc
} catch (err) {
console.error(err)
}
}
async function procOuput () {
const connection = await sql.promises.open(connectionString)
const spName = 'tmp_square'
await makeOutputProc(connection, spName)
try {
await runOutputProcWith(connection, spName, {
num: 15
})
await connection.promises.close()
} catch (err) {
console.error(err)
}
}
async function makeSelectProc (connection, spName) {
try {
const pm = connection.procedureMgr()
const def = `create or replace proc tmp_input_output
@len_last int output,
@len_first int output,
@first_last varchar(48) output,
@last_name varchar(30) = 'knowles',
@first_name varchar(18) = 'beyonce'
as begin
select @first_last = @first_name + " " + @last_name
select @len_first = len(@first_name)
select @len_last = len(@last_name)
select len(@first_last)
end`
await connection.promises.query(def)
const params = [
pm.makeParam(spName, '@len_last', 'int', 4, true),
pm.makeParam(spName, '@len_first', 'int', 4, true),
pm.makeParam(spName, '@first_last', 'varchar', 48, true),
pm.makeParam(spName, '@last_name', 'varchar', 30, false),
pm.makeParam(spName, '@first_name', 'varchar', 18, false)
]
const proc = pm.addProc(spName, params)
proc.setDialect(pm.ServerDialect.Sybase)
return proc
} catch (err) {
console.error(err)
}
}
async function procAsSelect () {
const connection = await sql.promises.open(connectionString)
const spName = 'tmp_input_output'
try {
const proc = await makeSelectProc(connection, spName)
const meta = proc.getMeta()
const select = meta.select
console.log(select)
const res = await connection.promises.query(select, ['Miley', 'Cyrus'])
console.log(JSON.stringify(res, null, 4))
await connection.promises.close()
} catch (err) {
console.error(err)
}
}
async function run () {
await procAsSelect()
await procOuput()
await proc()
await q1()
await promised()
}
run().then(() => {
console.log('done')
})
v2.4.5
-
electron v17 binaries
-
support for thread workers #228
master
const path = require('path')
const filePath = path.resolve(__dirname, './worker-item.js')
const { Worker } = require('worker_threads')
const worker1 = new Worker(filePath)
const worker2 = new Worker(filePath)
function dispatch (worker) {
worker.on('message', msg => {
switch (msg.command) {
case 'task_result': {
console.log(JSON.stringify(msg, null, 4))
}
}
})
worker.on('error', error => {
console.log(error)
})
}
dispatch(worker1)
dispatch(worker2)
function sendTask (worker, num) {
worker.postMessage(
{
command: 'task',
num: num
})
}
function clean () {
setTimeout(async () => {
console.log('exit.')
await Promise.all([
worker1.terminate(),
worker2.terminate()
])
}, 5000)
}
for (let i = 0; i < 40; i += 2) {
sendTask(worker1, i)
sendTask(worker2, i + 1)
}
clean()
worker
const { parentPort } = require('worker_threads')
const sql = require('msnodesqlv8')
const { GetConnection } = require('./get-connection')
const connectionString = new GetConnection().connectionString
async function compute (msg) {
try {
console.log(`worker receive task ${msg.num}`)
const conn = await sql.promises.open(connectionString)
const query = `select ${msg.num} as i, @@SPID as spid`
const res = await conn.promises.query(query)
await conn.promises.close()
parentPort.postMessage(
{
command: 'task_result',
data: `spid ${res.first[0].spid}`,
num: msg.num,
fib: getFib(msg.num)
})
} catch (e) {
parentPort.emit('error', e)
}
}
parentPort.on('message', async msg => {
switch (msg.command) {
case 'task': {
await compute(msg)
break
}
default: {
console.log(`unknown command ${msg.command}`)
break
}
}
})
function getFib (num) {
if (num === 0) {
return 0
} else if (num === 1) {
return 1
} else {
return getFib(num - 1) + getFib(num - 2)
}
}
v2.4.4
v2.4.3
patch node version to reload binaries