Executing Multiple Statements Using Knex & MySQL (Variables & Migration) - mysql

Problem:
I'm trying to add a new column to a table with tens of millions of rows, without blocking it.
I'm aware there are a few options here, and I've given ALTER TABLE a try with NULL values & also with a default value, but it takes a very long time (not workable) and locks the table.
So I'm trying to write a migration script that will duplicate the structure of the original table to a new table, add my new column to that new table, and then slowly migrate the old table data in to the new table.
My issue is with Knex though on the data copy.
I'm testing locally and the below query runs absolutely fine in MySQL version 5.6.34 in Sequel Pro, but I get a You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ' in Knex.
knex.schema.raw(`
SELECT #prevMaxIdMovedRecord;
SELECT #newMaxIdMovedRecord;
SELECT maxIdMoved FROM migration_status
ORDER BY maxIdMoved DESC
INTO #prevMaxIdMovedRecord;
INSERT IGNORE INTO table_copy (field1, newField1)
SELECT t.field1, 1 FROM table t
WHERE t.id BETWEEN #prevMaxIdMovedRecord AND #prevMaxIdMovedRecord + 50000;
SELECT id FROM table_copy
ORDER BY id DESC
LIMIT 1
INTO #newMaxIdMovedRecord;
INSERT INTO migration_status (maxIdMoved)
VALUES (#newMaxIdMovedRecord);
DELIMITER ;
`)
Any experts here that can assist? I can't see that Knex allows you to specify MySQL version in the Knexfile.js, and I was wondering if maybe there was a mismatch between versions on syntax. All my other commands in terms of new tables etc work fine...
Thanks

The mysql driver won't accept multiple statements in a single query by default. You can demonstrate this with a simple Node program:
const mysql = require('mysql')
connection = mysql.createConnection({
host: 'localhost',
user: 'youruser',
password: 'yourpassword',
database: 'yourdb',
multipleStatements: true
})
connection.connect()
connection.query('SELECT 1+1; SELECT 2+2;', function(err, results) {
if (err) {
throw err
}
console.log(results[0], results[1])
})
connection.end()
If you set multipleStatements to false, you should see an error. While multipleStatements could be passed to the driver via your knexfile.js,
{
client: "mysql",
connection: {
// ... other options ...
multipleStatements: true
}
}
and you might try this, I recommend you use a transaction as executing multiple statements in one query like this on a large table seems fragile at best. See docs for an introduction.

Related

Syntax error in SQL when inserting data via nodejs

I have a simple nodejs code in pipedream that sends the body email to mySQL Database.
i have checked the connection to database and its working.
Here is my code
const mysql = require('mysql2/promise');
const { host, port, username, password, database } = auths.mysql
const connection = await mysql.createConnection({
host,
port,//3306
user:"u648845344_demo",
password,
database,
});
const [rows, fields] = await connection.execute(
"INSERT INTO Testing (Email) VALUES (${JSON.stringify(steps.trigger.event.body.email)})"
);
console.log(rows);
//console.log(${JSON.stringify(steps.trigger.event.body.email)})
Error i am getting
ErrorYou have an error in your SQL syntax; check the manual that
corresponds to your MariaDB server version for the right syntax to use
near '{JSON.stringify(steps.trigger.event.body.email)})' at line 1 at
PromiseConnection.execute
(/tmp/ee/node_modules/mysql2/promise.js:110:22) at
Object.module.exports (/steps/mysql.js:14:41) at process._tickCallback
(internal/process/next_tick.js:68:7)
i tried getting email on console log but then error i am getting is
TypeError [ERR_INVALID_ARG_TYPE]The first argument must be one of type
string, Buffer, ArrayBuffer, Array, or Array-like Object. Received
type undefined
This is a classic SQL injection bug, and it's easily fixed by using prepared statements:
const [rows, fields] = await connection.execute(
"INSERT INTO Testing (Email) VALUES (?)",
[ steps.trigger.event.body.email ]
);
If you write your queries without data, just placeholders, and use methods like this to add the data to the query via the driver you will not create any SQL injection bugs. These are an extremely serious form of bug because a single one, if discovered, could lead to a catastrophic outcome for you, your project and any business you're working for.
Using JSON.stringify for SQL protection is, and I cannot stress this enough, completely and wildly inappropriate. That escapes JSON and only JSON. You must use SQL-specific escaping functions if that occasion arises, but use prepared statements with placeholder values whenever possible.

Sequelize Run Script File

I have a project which is using Sequelize to manage a set of MySQL databases. Thus far I've been able to run simple queries to create new databases, insert parameters into a table, and select data... however, I have a very long .sql file (+1,700 lines) which when executed will set up a database with a specific schema (ie. tables, views, etc.). The problem is that I can not figure out how to execute a script like this using sequelize. I know the script works on a new database because I can execute the sql file from MySQL Workbench, however I do not know how to execute the script from javascript file using sequelize. I've searched forums but can't seem to find any resources either. Can this be done?
You can run raw query by Sequelize using sequelize.query(sql_string)
and you can use fs or fs-extra to read the sql file;
Just mind that you need to set the multiline statement option true in order to run this sql text:
var sql_string = fs.readFileSync('path to file', 'utf8');
const sequelize = new Sequelize('database', 'username', 'password', {
host: 'localhost',
dialect: /* one of 'mysql' | 'mariadb' | 'postgres' | 'mssql' */,
dialectOptions: {
multipleStatements: true
}
});
sequelize.query(sql_string);
Edit 1:
To better understanding of Sequelize class take a look at this

In Ruby on Rails, how can I send multiple update statements to the database using the dafult db connection object?

For performance reasons, I need to issue SQL statements (insertions and updates) directly to the database. I have no problem executing a large insert statement like:
#conn = ActiveRecord::Base.connection
inserts = "INSERT INTO clients (code, name) VALUES ('abc123', 'Alyx'), ('xyz123', 'Gordon') ...many more...\;"
#conn.execute inserts
However, I'm having difficulty executing a batch of updates like:
updates = "UPDATE clients SET name='Julia' WHERE id=1; UPDATE clients SET name='Eli' WHERE id=2; ...many more..."
#conn.execute updates
# or
#conn.update updates
because that gives me the general SQL syntax error:
ActiveRecord::StatementInvalid: Mysql2::Error: You have an error in your SQL syntax;
I've tried changing the database.yml configuration file to include the MULTI_STATEMENTS flag without success:
flags:
- MULTI_STATEMENTS
The only way I managed to make this work is by getting a Mysql2 client instance, with the flag set:
client = Mysql2::Client.new(host: 'localhost', ... , flags: Mysql2::Client::MULTI_STATEMENTS)
client.query updates
but this doesn't seem like a good idea since it would lock the app together with the mysql2 gem.
Is this a problem with the mysql2 gem, ActiveRecord, or am I missing something essential?
So, I found there was no reason to keep using ActiveRecord since I wasn't making use of it so I decided to stick with Mysql2::Client.
Just make sure that flags: Mysql2::Client::MULTI_STATEMENTS is set and remember to clear the results from any previous commands before issuing another one:
while client.next_result
end
Also, the reason behind trying to use ActiveRecord was the transaction management. It's possible to do the same with:
client.query 'BEGIN'
client.query 'COMMIT'
client.query 'ROLLBACK'

NodeJS + mysql: using connection pool leads to deadlock tables

I am using NodeJS and mysql library to access MySQL database.
When I establish single connection and repeatedly use it, it works fine:
global.mysql = mysql_module.createConnection({
host: config.mysql.host,
user: config.mysql.user,
password: config.mysql.password
});
When I use connection pool instead, I get ER_LOCK_WAIT_TIMEOUT errors in transactions.
global.mysql = mysql_module.createPool({
host: config.mysql.host,
user: config.mysql.user,
password: config.mysql.password,
database : config.mysql.database,
connectionLimit : 50
});
Strangely enough, the errors do occur on exactly the same data at exactly the same times.
I.e. I have transaction in which I insert in three tables in a row, each time using last inserted ID from previous INSERT statement. With some data this works fine, with some data, the third INSERT produces ER_LOCK_WAIT_TIMEOUT error. When I use single connection in NodeJS, this works fine, so this must be problem related to connection pool.
Any help would be appreciated.
Not a big fan of answering my own questions, but this problem is solved by explicitly creating connection from a pool and using it for every sql statement during transaction
pool.getConnection(function(err, connection) {
connection.query( 'START TRANSACTION', function(err, rows) {
// do all sql statements with connection and then
connection.query( 'COMMIT', function(err, rows) {
connection.release();
}
});
});

Rails & MySQL: SELECT Statement Single vs. Double Quotes

I have a CRON job which executes a SELECT statement to grab records. When the SELECT runs on my dev machine, it produces the following statement:
SELECT `users`.* FROM `users` WHERE `users`.`id` = 87 LIMIT 1
This is successful.
When the SELECT runs on my production (hosted) machine it produces the statement with double quotes:
SELECT "users".* FROM "users" WHERE "users”.”id” = 87 LIMIT 1
This is not successful and I get a MySQL 1064 error,
#1064 - You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '.* FROM "users" WHERE "users
The code is the same on both machines, but my dev MySQL is version 5.5.33, whereas production is 5.1.67 (I don't have control over this to set/update it)
Is there a way to force single quotes or another preferred method to handle this situation?
Thanks for your time and assistance.
--EDIT--
Here are the main code snippets that are invoked via my CRON job:
/lib/tasks/reports.rake
namespace :report do
desc "Send Daily Report"
task :daily => :environment do
User.where(:report_daily => 1).find_each do |user|
ReportsMailer.send_report(user, 'daily').deliver
end
end
/app/mailers/reports_mailer.rb
def send_report(user, date_increment)
#user = user
#date_increment = date_increment
get_times(user)
mail :to => user.email, :subject=> "Report: #{#dates}"
end
--EDIT2--
So it looks like I need to use slanted single quotes (`) in order for this to work successfully. How do I force my app or MySQL to use these instead of double (") quotes?
I don't know why it does this, but I do know that if you're referencing column names in MYSQL, you need to use ``, whereas values / data should be wrapped in "", like this:
SELECT `users`.* FROM `users` WHERE `users`.`id` = "87" LIMIT 1
I learnt this the hard way back in the day when I was learning how to do simple MYSQL queries
Here's some documentation from MYSQL's site for you:
The identifier quote character is the backtick (“`”):
mysql> SELECT * FROM `select` WHERE `select`.id > 100;
Identifier quote characters can be included within an identifier if
you quote the identifier. If the character to be included within the
identifier is the same as that used to quote the identifier itself,
then you need to double the character. The following statement creates
a table named a`b that contains a column named c"d:
mysql> CREATE TABLE `a``b` (`c"d` INT);
Is there any reason you couldn't put some of your sql statement directly into your code like:
User.where("`report_daily`=1").find_each do |user|
After further inspection, and working with my hosting company, its turns out that my query is timing out on their server. Thanks to all that responded.
Since you are not using any literals, the format of the generated SQL statements should be determined by the underlying adapter. Perhaps you have a different mysql adapter installed or configured on each machine. Check the installed version. For example:
bundle show mysql
and also check the adapter configuration for your project in database.yml. For example:
adapter: mysql
A comparison of the results of these checks between each machine should tell you if you are using different adapters on the two machines.