Number of '?'s in statement string does not match argument count

I'm trying to make a synchronism with my API using WebSQL, but I'm getting the following error:

number of '?'s in statement string does not match argument count.

I was following this tutorial

I don't know what is going on, what am I missing here?

function createTable() {
            db.transaction(
                function (tx) {

                    tx.executeSql('CREATE TABLE IF NOT EXISTS COUNTRY (id, countryName, countryCode)');
                    tx.executeSql('CREATE TABLE IF NOT EXISTS LOCATION (id, locationName)');
                    tx.executeSql("CREATE TABLE IF NOT EXISTS clientes (" +
                    "clientes_id Integer PRIMARY KEY AUTOINCREMENT, " +
                    "_criado Text, " +
                    "_modificado Text, " +
                    "_status Text, " +
                    "id_rm Integer, " +
                    "credencial_id Integer, " +
                    "informacao_adicional, " +
                    "nome, " +
                    "tipo, " +
                    "CONSTRAINT unique_clientes_id UNIQUE ('clientes_id'))");
                    tx.executeSql('CREATE INDEX IF NOT EXISTS "clientes.index_clientes_id" ON "clientes"("clientes_id");');



                },
                txErrorHandler,
                function () {
                    log('clientes table created successfully');
                }
            );
        }
function getLastSync() {
            db.transaction(
                function (tx) {
                    var sql = "SELECT MAX(_modificado) as lastS FROM clientes";


                    tx.executeSql(sql,
                        function (tx, results) {


                            var lastSync = results.rows.item(0).lastS;
                            console.log(lastSync);

                        }
                    );

                },
                txErrorHandler,
                function () {
                    log('error');
                }
            );
        }

        function getChanges(syncURL, modifiedSince) {


            ServiceClientes.getAll(syncURL, modifiedSince).success(function (data) {
                log("The server returned " + data.length + " changes that occurred after " + modifiedSince);
            }).error(function (error) {
                console.log(error);
            });


        }

        function applyChanges() {

            angular.forEach(data.dados, function (item) {
                db.transaction(
                    function (tx) {
                        log(item.nome + " : " + item.tipo_pessoa);

                        tx.executeSql('INSERT OR REPLACE INTO clientes (nome, tipo, ' +
                        '_criado,' +
                        '_modificado , ' +
                        '_status, ' +
                        'id_rm, ' +
                        'informacao_adicional ) VALUES (?,?,?,?,?,?,?)',
                        [item.nome, item.tipo_pessoa, item.criado, item.modificado, item.status, item.id, item.informacoes_adicionais]);
                    },
                    txErrorHandler,
                    function (tx) {
                    }
                );
            });
        }

        function sync(syncURL) {


            getLastSync(function (lastSync) {
                getChanges(syncURL, lastSync, function (changes) {
                        if (changes.length > 0) {
                            applyChanges(changes);
                        } else {
                            console.log('Nothing to synchronize');
                        }

                    }
                );
            });

        }

I can see two problems here:

  1. executeSql takes 3 parameters: sql, params, callback. Both params and callback are optional but if you want to specify callback you HAVE TO specify params too.
  2. Don't concatenate values provided by user. You can end up with sql-incetion. Instead use parameters.

    function createTable() {
        db.transaction(
            function (tx) {
    
                tx.executeSql('CREATE TABLE IF NOT EXISTS COUNTRY (id, countryName, countryCode)');
                tx.executeSql('CREATE TABLE IF NOT EXISTS LOCATION (id, locationName)');
                tx.executeSql("CREATE TABLE IF NOT EXISTS clientes (" +
                "clientes_id Integer PRIMARY KEY AUTOINCREMENT, " +
                "_criado Text, " +
                "_modificado Text, " +
                "_status Text, " +
                "id_rm Integer, " +
                "credencial_id Integer, " +
                "informacao_adicional, " +
                "nome, " +
                "tipo, " +
                "CONSTRAINT unique_clientes_id UNIQUE ('clientes_id'))");
                tx.executeSql('CREATE INDEX IF NOT EXISTS "clientes.index_clientes_id" ON "clientes"("clientes_id");');
    
    
    
            },
            txErrorHandler,
            function () {
                log('clientes table created successfully');
            }
        );
    }
    function getLastSync() {
        db.transaction(
            function (tx) {
                var sql = "SELECT MAX(_modificado) as lastS FROM clientes";
    
    
                tx.executeSql(sql, [],
                    function (tx, results) {
    
    
                        var lastSync = results.rows.item(0).lastS;
                        console.log(lastSync);
    
                    }
                );
    
            },
            txErrorHandler,
            function () {
                log('error');
            }
        );
    }
    
    function getChanges(syncURL, modifiedSince) {
    
    
        ServiceClientes.getAll(syncURL, modifiedSince).success(function (data) {
            log("The server returned " + data.length + " changes that occurred after " + modifiedSince);
        }).error(function (error) {
            console.log(error);
        });
    
    
    }
    
    function applyChanges() {
    
        angular.forEach(data.dados, function (item) {
            db.transaction(
                function (tx) {
                    log(item.nome + " : " + item.tipo_pessoa);
    
                    tx.executeSql('INSERT OR REPLACE INTO clientes (nome, tipo, ' +
                    '_criado,' +
                    '_modificado , ' +
                    '_status, ' +
                    'id_rm, ' +
                    'informacao_adicional ) VALUES (?,?,?,?,?,?,?)',[item.nome, item.tipo_pessoa, item.criado, item.modificado, item.status, item.id,item.informacoes_adicionais]);
                },
                txErrorHandler,
                function (tx) {
                }
            );
        });
    }
    
    function sync(syncURL) {
    
    
        getLastSync(function (lastSync) {
            getChanges(syncURL, lastSync, function (changes) {
                    if (changes.length > 0) {
                        applyChanges(changes);
                    } else {
                        console.log('Nothing to synchronize');
                    }
    
                }
            );
        });
    
    }
    

You should utilise second argument of executeSql to pass your data into parametrised SQL query, rather than concatenate them. Concatenating is a bad practise and you may have a quote or question mark in one of the fields which could result in error you describe.

So use something like:

tx.executeSql('INSERT OR REPLACE INTO clientes (nome, tipo, ' +
              '_criado,' +
              '_modificado , ' +
              '_status, ' +
              'id_rm, ' +
              'informacao_adicional ) VALUES (?,?,?,?,?,?,?)',
       [item.nome, item.tipo_pessoa, item.criado, item.modificado, item.status, item.id, item.informacoes_adicionais]);