3

I'm puzzling over a weird problem I cannot replicate.

Scenario

I wrote a simple nodejs application that, after some UI interaction append some records to an Access 97 database. I'm using node-adodb to connect with it.

Some relevant piece of code.

var DBDATA = require('node-adodb'), dbConnection = DBDATA.open('Provider=Microsoft.Jet.OLEDB.4.0;Data Source=/path/to/my/file.mdb');

function append(data, callback)
{
  var table;
  var query;
  var array = [];

  array.push({name: "Date", value: formatDate(data.date)});
  array.push({name: "Time", value: formatTime(data.date)});
  array.push({name: "Type", value: Number(data.Type)});
  array.push({name: "Value", value: Number(exists(data.value, 0))});
  // ...other fields

  var fields = array.map(function (e) {
      return "[" + e.name + "]";
  }).join(",");

  var values = array.map(function (e) {
      return e.value;
  }).join(",");

  table = "tblData";
  query = 'INSERT INTO ' + table + '(' + fields + ') ' + 'VALUES (' + values + ')';
  dbConnection
      .execute(query)
      .on('done', function (data) {
          return callback({id: id, success: true});
      })
      .on('fail', function (data) {
          console.log(data);
          return callback({id: id, success: false});
      });      
}

The issue

The above function is called whenever a new record is ready. Usually it works fine, but it happens about 1 time per week (among hundreds of records) that I find in the database multiple rows identical.

Due to the nature of the information this is impossible - I mean, it's impossible that the actual data is the same.

I guessed for a bug in the caller, that for some reasons sends me the same variable's content. Hence I added a check before append the record.

What I tried to do

function checkDuplicate(table, array, callback)
{
  var query = "SELECT * FROM " + table + " WHERE ";
  array.forEach(function(element)
  {
    query += "([" + element.name + "]=" + element.value + ") AND ";
  });
  query = query.substr(0, query.length - 4);

  dbConnection
    .query(query)
    .on("done", function (data) {
      return callback(data.records.length > 0);
    })
    .on("fail", function (data) {
        return callback(false);
    });
}

in the append function I call this one and if it returns a value > 0 I don't execute the query, because it would mean there already is the same row.

Testing it with fake data gave good results: no multiple records were added. Unfortunately, this didn't fixed the issue in the real world. After 20 days I noticed that a row was added three times.

Questions

  1. Do you see any evidence of a major mistake in my approach?
  2. Is there a more reliable way to avoid this problem?

Please note I cannot change the database structure because it's not mine.

UPDATE

This is the new code I'm using:

// Add only if there isn't an identical record
query = 'INSERT INTO ' + table + '(' + fields + ') ';
query += ' SELECT TOP 1 ' + values;
query += ' FROM ' + table;
query += ' WHERE NOT EXISTS ( SELECT 1 FROM ' + table + ' WHERE ';

 array.forEach(function(element)
 {
     query += "([" + element.name + "]=" + element.value + ") AND ";
 });
 query = query.substr(0, query.length - 4);
 query += ' );';

 dbConnection
     .execute(query)
     .on('done', function (data) {
          return callback({id: id, success: true});
     })
     .on('fail', function (data) {
          console.log(data);
          return callback({id: id, success: false});
      });      

but it doesn't solved the problem, i.e. sometimes I still found two or more records identical in the database.

I'm afraid it could be the same behavior: the client make multiple requests in a while and they are executed in parallel, so each one doesn't find the record, and all will be add it.

Hance, what is the right approach to avoid this without change the database structure? Is there a way to force node-adodb to execute only one query at time?

Mark
  • 4,338
  • 7
  • 58
  • 120
  • 1
    Javascript works parallel (async). That means, it would request data 3 times in almost the same timespan. After that, it would insert the data. That means it gets `0` returned all three times, and store the data three times after that. – Randy Nov 14 '17 at 14:48
  • Is it possible to force that function to work sync? Or what other tools I have to avoid the issue? – Mark Nov 14 '17 at 15:10
  • 1
    Checking existing records can be made even in sql – Maxim Nov 14 '17 at 15:32
  • @Maxim something like this? https://stackoverflow.com/a/4253987/881712 – Mark Nov 14 '17 at 18:30
  • @Mark Yes, but probably ms access has some different syntax – Maxim Nov 16 '17 at 07:45
  • @Maxim, unfortunately that didn't work. Please see the updated question. – Mark Nov 26 '17 at 11:11
  • What locking are you using for the database. In a multiple user situation, it is possible with optimistic locking that the queries happen somewhat concurrently and the new record one is not yet committed in the database before new record two is inserted. Pessimistic locking would solve this problem. You only need to do it on this application. – Perry Sugerman Apr 26 '19 at 02:24

0 Answers0