Releases: TimelordUK/node-sqlserver-v8
v2.4.0
-
electron v14/v15 binaries included for Linux, Windows and Mac
promises
see promises.ts under samples/typescript for some example code of how to use these promise methods.
Some promises have been added to the API for a more modern async approach. They are all collected under 'object.promises.promise' (pool.promises.open(), sql.promises.query(..), sql.promises.callProc(..), connection.promises.query(..) etc)
see index.js for definitions
export interface AggregatorPromises {
query(sql: string, params?: any[], options?: QueryAggregatorOptions): Promise<QueryAggregatorResults>
callProc(name: string, params?: any, options?: QueryAggregatorOptions): Promise<QueryAggregatorResults>
}
interface SqlClientPromises {
query(conn_str: string, sql: string, params?: any[], options?: QueryAggregatorOptions): Promise<QueryAggregatorResults>
callProc(conn_str: string, name: string, params?: any, options?: QueryAggregatorOptions): Promise<QueryAggregatorResults>
open(conn_str: string): Promise<Connection>
}
export interface PoolPromises extends AggregatorPromises {
open(): Promise<Pool>
close(): Promise<any>
}
interface ConnectionPromises extends AggregatorPromises {
prepare(sql: string): Promise<PreparedStatement>
getTable(name: string): Promise<BulkTableMgr>
close(): Promise<any>
cancel(name: string): Promise<any>
}
export interface BulkTableMgrPromises
{
select(cols: any[]): Promise<any[]>
insert(rows: any[]): Promise<any>
delete(rows: any[]): Promise<any>
update(rows: any[]): Promise<any>
}
export interface PreparedPromises {
free(): Promise<any>
query(params?: any[], options?: QueryAggregatorOptions) : Promise<QueryAggregatorResults>
}
for example using the connection pool using promises.
connection pool
async function pool() {
try {
const connStr: string = getConnection()
const size = 4
const options: PoolOptions = {
connectionString: connStr,
ceiling: size
}
const pool: Pool = new sql.Pool(options)
await pool.promises.open()
const all = Array(size * 2).fill(0).map((_, i) => pool.promises.query(`select ${i} as i, @@SPID as spid`))
const promised: QueryAggregatorResults[] = await Promise.all(all)
const res = promised.map(r => r.first[0].spid)
await pool.promises.close()
console.log(`pool spids ${res.join(', ')}`)
} catch (e) {
console.log(e)
}
}
query
async function adhocQuery() {
try {
const connStr: string = getConnection()
const res: QueryAggregatorResults = await sql.promises.query(connStr, 'select @@SPID as spid')
console.log(`ashoc spid ${res.first[0].spid}`)
} catch (e) {
console.log(e)
}
}
async function openSelectClose() {
try {
const connStr: string = getConnection()
const conn: Connection = await sql.promises.open(connStr)
const res: QueryAggregatorResults = await conn.promises.query('select @@SPID as spid')
console.log(JSON.stringify(res, null, 4))
await conn.promises.close()
} catch (e) {
console.log(e)
}
}
procedure
use a promise to open connection, call a proc and close all from one promise - or call from a connection. Note all results are aggregated i.e. you are returned a result containing all queries etc
async function adhocProc() {
try {
const connStr: string = getConnection()
const proc = new ProcTest(connStr, sampleProc)
await proc.create()
const msg = 'hello world'
const res: QueryAggregatorResults = await sql.promises.callProc(connStr, sampleProc.name, {
param: msg
})
await proc.drop()
console.log(`adhocProc returns ${res.returns} from param '${msg}''`)
} catch (e) {
console.log(e)
}
}
async function proc() {
try {
const connStr: string = getConnection()
const proc = new ProcTest(connStr, sampleProc)
await proc.create()
const conn: Connection = await sql.promises.open(connStr)
const promises: ConnectionPromises = conn.promises
const msg = 'hello world'
const res: QueryAggregatorResults = await promises.callProc(sampleProc.name, {
param: msg
})
console.log(`proc returns ${res.returns} from param '${msg}''`)
await proc.drop()
await promises.close()
} catch (e) {
console.log(e)
}
}
table manager
use a promise to fetch a table and insert rows to it.
async function table() {
try {
const connStr: string = getConnection()
const connection = await sql.promises.open(connStr)
const tm: BulkTableTest = new BulkTableTest(connection, sampleTableDef)
const table: BulkTableMgr = await tm.create()
const vec: SampleRecord[] = getInsertVec(10)
console.log(`table = ${tm.createTableSql}`)
await table.promises.insert(vec)
const read = await connection.promises.query(tm.selectSql)
console.log(`table ${read.first.length} rows from ${tm.tableName}`)
console.log(JSON.stringify(read.first, null, 4))
await tm.drop()
await connection.promises.close()
} catch (e) {
console.log(e)
}
}
v2.2.0
-
electron v13 binaries
-
BCP allows fast insert speed from client to a designated table. This is achieved via allocating fixed positions in memory binding each column on that table and re-populating/sending each row to the server. It is in effect a memory copy from the client to the table.
a 16 column Employee table mixed with binary, varchar, date, int and decimal can insert over 50k rows in 3 seconds (vs 25 seconds using non bcp) over a network, smaller tables speeds can be over 100k a second.
a 7 column table as shown inserts 100k rows in 3.5 seconds over a network - and that includes selecting 100 back to verify insert.
√ bcp employee (1144ms)
√ bcp 7 column mixed table (3753ms)
√ bcp expect error null in non null column (441ms)
√ bcp expect error duplicate primary key (68ms)
√ bcp recovery from error. (73ms)
√ bcp hierarchyid binary (165ms)
√ bcp small binary (105ms)
√ bcp bit bit (1379ms)
√ bcp uniqueidentifier (98ms)
√ bcp smallint (97ms)
√ bcp tinyint (90ms)
√ bcp real with null (90ms)
√ bcp real (89ms)
√ bcp bigint with nulls (82ms)
√ bcp bigint (100ms)
√ bcp time (99ms)
√ bcp numeric (103ms)
√ bcp varchar(max) (10k chars) (1806ms)
√ bcp datetimeoffset datetimeoffset - mix with nulls (145ms)
√ bcp datetimeoffset datetimeoffset (239ms)
√ bcp binary binary - mix with nulls (197ms)
√ bcp binary binary (203ms)
√ bcp bit bit - mix with nulls (101ms)
√ bcp timestamp timestamp - mix with nulls (119ms)
√ bcp timestamp timestamp - no null (127ms)
√ bcp varchar varchar with nulls (172ms)
√ bcp varchar varchar (139ms)
√ bcp int, int column - with nulls (108ms)
√ bcp int, int column (125ms)
29 passing (12s)
test('bcp 7 column mixed table ', testDone => {
async function test () {
function getNumeric (i) {
const v = Math.sqrt(i + 1)
return Math.round(v * 1e6) / 1e6
}
const bcp = new BcpEntry({
tableName: 'test_table_7_bcp',
columns: [
{
name: 'id',
type: 'INT PRIMARY KEY'
},
{
name: 's1',
type: 'VARCHAR (255) NULL'
},
{
name: 's2',
type: 'VARCHAR (100) NULL'
},
{
name: 'i1',
type: 'int null'
},
{
name: 'i2',
type: 'int NULL'
},
{
name: 'n1',
type: 'numeric(18,6) NULL'
},
{
name: 'n2',
type: 'numeric(18,6) NULL'
}]
}, i => {
return {
id: i,
s1: i % 2 === 0 ? null : `column1${i}`,
s2: `testing${i + 1}2Data`,
i1: i * 5,
i2: i * 9,
n1: getNumeric(i),
n2: getNumeric(i)
}
}, (actual, expected) => {
assert.deepStrictEqual(actual.length, expected.length)
for (let i = 0; i < actual.length; ++i) {
const lhs = actual[i]
const rhs = expected[i]
assert.deepStrictEqual(lhs.id, rhs.id)
assert.deepStrictEqual(lhs.s1, rhs.s1)
assert.deepStrictEqual(lhs.s2, rhs.s2)
assert.deepStrictEqual(lhs.i1, rhs.i1)
assert.deepStrictEqual(lhs.i2, rhs.i2)
assert(Math.abs(lhs.n1 - rhs.n1) < 1e-5)
assert(Math.abs(lhs.n2 - rhs.n2) < 1e-5)
}
})
return await bcp.runner(100000)
}
test().then((e) => {
testDone(e)
})
})
// see bcp.js unit tests - bind to a table
async create () {
const promisedQuery = util.promisify(theConnection.query)
const tm = theConnection.tableMgr()
const promisedGetTable = util.promisify(tm.getTable)
await promisedQuery(this.dropTableSql)
await promisedQuery(this.createTableSql)
const table = await promisedGetTable(this.tableName)
return table
}
}
// set the flag to turn on bcp and send rows to server using fast memory copy.
theConnection.setUseUTC(false)
const table = await helper.create()
table.setUseBcp(true)
const promisedInsert = util.promisify(table.insertRows)
const promisedQuery = util.promisify(theConnection.query)
This protocol is not part of the ODBC specification and its use therefore depends on using correct ODBC driver. For linux users, this should work out the box as ODBC 17 is the only driver supported and this is one used for BCP. The feature has been tested on Ubuntu, MacOS, Debian and Alpine.
For windows users, older drivers can still be used on all non bcp functions just as before - however presently only ODBC 17 is supported for bcp. Hence you need to have installed ODBC data source "ODBC Driver 17 for SQL Server". No other driver will work and attempts to do so will probably crash the node instance.
v2.1.0
This is a fairly significant release, please test carefully before deploying to production environments.
- node v16 binaries on all platforms.
- fix where cancel query may cause node crash due to thread unsafe call
- incorrect bound types may cause hung query.
- mix of error / info during query may cause hung query in callback mode
- improved typescript typings #207
- improved local date management #205
- useUTC option on pool
- boundTable.useMetaType(true) send column types for bound tables rather than driver deducing types from data.
- utility promise await poolOrConnection.callprocAggregator('sp_proc', [], { timeoutMs: procTimeout, raw: false })
v2.0.15
- add binary support for node v16
- #203
stored proc call via a pool or connection
see unit tests for sproc or samples\javascript\procedure.js
// connectionProxy can be a pool or connection
const q = connectionProxy.callproc(spName, o, (err, results, output, more) => { })
javascript git(master) node .\procedure.js
Debugger attached.
asConnection 1000 invocations, elapsed = 1427, res length=1000
pool opened : {
"floor": 0,
"ceiling": 5,
"heartbeatSecs": 20,
"heartbeatSql": "select @@SPID as spid",
"inactivityTimeoutSecs": 60,
"connectionString": "Driver={ODBC Driver 17 for SQL Server};Server=(localdb)\\node;Database=scratch;Trusted_Connection=yes;"
}
asPool [5] 1000 invocations, elapsed = 230, res length=1000
done
Waiting for the debugger to disconnect...
v2.0.14
fix for lower node versions
v2.0.13
v2,0,12
test('use tableMgr bulk insert varchar vector - exactly 4000 chars', testDone => {
async function runner () {
const b = repeat('z', 4000)
const helper = new TypeTableHelper(theConnection, 'NVARCHAR(MAX)')
const expected = helper.getVec(10, i => b)
const table = await helper.create()
const promisedInsert = util.promisify(table.insertRows)
const promisedSelect = util.promisify(table.selectRows)
try {
await promisedInsert(expected)
const res = await promisedSelect(expected)
assert.deepStrictEqual(res, expected)
} catch (e) {
assert.ifError(e)
}
}
runner().then(() => {
testDone()
})
})
v2.0.11
bulk insertion where vector includes nulls causes unexpected behvior - 'null' txt in db column or node crashes for varbinary
test('use tableMgr bulk insert varbinary vector - with null', testDone => {
async function runner () {
const b = Buffer.from('0102030405060708090a', 'hex')
const helper = new TypeTableHelper(theConnection, 'varbinary(10)')
const expected = helper.getVec(10, i => i % 2 === 0 ? null : b)
const table = await helper.create()
const promisedInsert = util.promisify(table.insertRows)
const promisedSelect = util.promisify(table.selectRows)
try {
await promisedInsert(expected)
const res = await promisedSelect(expected)
assert.deepStrictEqual(res, expected)
} catch (e) {
assert.ifError(e)
}
}
runner().then(() => {
testDone()
})
})
v2.0.10
add macos to readme
v2.0.9
- #193 - table manager bug with update SQL signature.
- #183 - transaction insert with constraint violation streaming errors fix
- #173 - alpine linux binaries included
- #189 - MacOS support / linux binaries includes - thanks for PR
- new sample code showing how to throttle, tvp and geography
- promsie safe variants of getTable and getProc
- move to class based JS