Skip to content

Releases: TimelordUK/node-sqlserver-v8

v2.7.0

09 Oct 17:49
Compare
Choose a tag to compare
  1. Node 19 binaries.
  2. 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

10 Jul 18:50
Compare
Choose a tag to compare
  1. #254 Segmentation Fault while attempting to use Table Value Paramaters
  2. #255 Bulk insert not possible for tables that contain timestamp
  3. leak when using promise query on prepared statements.
  4. Driver={ODBC Driver 18 for SQL Server} support on all platforms
  5. electron v19 binaries
  6. linting cpp reduce build warnings
  7. upload node 18 32 bit windows
  8. add windows electron v20, v21

v2.5.0

12 Jun 18:36
Compare
Choose a tag to compare
  1. PR fix - thanks #251

  2. pool extended with additional promises to match connection #246

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>
}
  1. prepared statements with nvarchar(max) #248

  2. prepared statement may cause node crash due to stack corruption.

  3. 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)
  })
  1. 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

08 May 15:43
Compare
Choose a tag to compare
  1. pool PR fix- thanks #244
  2. electron v18
  3. 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

13 Mar 18:21
Compare
Choose a tag to compare
  1. manually register a table - works with Sybase or SqlServer
  2. #240
  3. #237
  4. v18 binaries
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

26 Feb 16:35
Compare
Choose a tag to compare
  1. manually register a stored procedure (See example below)
  2. 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

29 Jan 18:13
Compare
Choose a tag to compare
  1. electron v17 binaries

  2. 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

10 Dec 18:16
Compare
Choose a tag to compare
  1. #226
  2. Node v.17 on linux (not alpine), MacOS, Windows
  3. Electron v.16 on all platforms

v2.4.3

13 Nov 12:48
Compare
Choose a tag to compare
patch node version to reload binaries

v2.4.2

06 Nov 20:23
Compare
Choose a tag to compare

typo in binding.gyp preventing apple silicon compilation

  1. #214