Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use mysql2 instead of Mysql #1

Merged
merged 15 commits into from
Apr 18, 2022
58 changes: 58 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
name: Build

on:
push:
branches: [ master ]
pull_request:
branches: [ master ]

jobs:
build:
runs-on: ubuntu-latest

strategy:
matrix:
node-version: [12.x, 14.x, 16.x]

services:
mysql:
image: mysql:latest
ports:
- 3306:3306
env:
MYSQL_USER: testuser
MYSQL_PASSWORD: testpassword
MYSQL_DATABASE: "mysql-import-test-db-1"
MYSQL_ROOT_PASSWORD: roottestpassword
options: --health-cmd="mysqladmin ping" --health-interval=5s --health-timeout=2s --health-retries=3

steps:
- uses: actions/checkout@v3

- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}

- name: Verify MySql connection
run: |
while ! mysqladmin ping -h"127.0.0.1" --silent; do
sleep 1
done

- name: Build
env:
DB_HOST: '127.0.0.1'
DB_USER: 'root'
DB_PASSWORD: 'roottestpassword'
DB_DATABASE: 'mysql-import-test-db-1'
run: |
npm ci
npm run test
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v2
with:
token: ${{ secrets.CODECOV_TOKEN }}
directory: ./coverage
fail_ci_if_error: true
verbose: true
6 changes: 0 additions & 6 deletions .travis.yml

This file was deleted.

100 changes: 96 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,99 @@
This is a fork from [Pamblam/mysql-import](https://github.com/Pamblam/mysql-import)

The reason for this fork is that we ran into some performance issues for long SQL statements since the major upgrade to 5.x.
We made an issue [#47](https://github.com/Pamblam/mysql-import/issues/47) and PR in which we solve the performance issue, at least for our case.
Unfortunately, the PR has not yet been accepted and merged by the original maintainer.
## Installation
```
npm install @pivvenit/mysql-import
```

We will periodically update this fork with changes from the upstream.
## Changes from [Pamblam/mysql-import]
- Fixed performance issues with long SQL statement parsing ([#47](https://github.com/Pamblam/mysql-import/issues/47), however not merged by the maintainer)
- Fixed issue with MySQL 8 authentication, now uses `mysql2` instead of `mysql` ([#50](https://github.com/Pamblam/mysql-import/issues/50))
- Use github actions instead of Travis
- Made tests independent of ordering

In case the upstream author accepts our pull request, we will be merging it into the upstream repository.

## Usage
```js
const host = 'localhost';
const user = 'root';
const password = 'password';
const database = 'mydb';

const Importer = require('@pivvenit/mysql-import');
const importer = new Importer({host, user, password, database});

// New onProgress method, added in version 5.0!
importer.onProgress(progress=>{
var percent = Math.floor(progress.bytes_processed / progress.total_bytes * 10000) / 100;
console.log(`${percent}% Completed`);
});

importer.import('path/to/dump.sql').then(()=>{
var files_imported = importer.getImported();
console.log(`${files_imported.length} SQL file(s) imported.`);
}).catch(err=>{
console.error(err);
});
```

## API

## Methods

### `new Importer({host, user, password[, database]})`

The constructor requires an object with a `host`, `user`, and `password` parameter. Passing in a database parameter is optional.

### `Importer.prototype.getImported()`

Get an array of files imported.

### `Importer.prototype.setEncoding(encoding)`

Set the encoding to use when reading import files. Supported arguments are: `utf8`, `ucs2`, `utf16le`, `latin1`, `ascii`, `base64`, or `hex`.

### `Importer.prototype.use(database)`

Set or change the database to import to.

### `Importer.prototype.onProgress(callback)`

*(New in v. 5.0!) -* Set a callback to be called as the importer processes chunks of the dump file. Callback is provided an object with the following properties:

- `total_files`: The total files in the queue.
- `file_no`: The number of the current dump file in the queue.
- `bytes_processed`: The number of bytes of the file processed.
- `total_bytes`: The size of the dump file.
- `file_path`: The full path to the dump file.

### `Importer.prototype.onDumpCompleted(callback)`

*(New in v. 5.0!) -* Set a callback to be called after each dump file has completed processing. Callback is provided an object with the following properties:

- `total_files`: The total files in the queue.
- `file_no`: The number of the current dump file in the queue.
- `file_path`: The full path to the dump file.
- `error`: If there was an error, the error object; if no errors, this will be `null`.

### `Importer.prototype.import(...input)`

Import an `.sql` file or files into the database. This method will take...

- Any number of paths to individual `.sql` files.
```
importer.import('path/to/dump1.sql', 'path/to/dum2.sql')
```
- Any number of paths that contain any number of `.sql` files.
```
importer.import('path/to/mysqldumps/')
```
- Any number of arrays containing either of the above.
```
importer.import(['path/to/dump.sql', 'path/to/dumps/'])
```
- Any combination of any of the above.

### `Importer.prototype.disconnect(graceful=true)`

Disconnects the connection. If `graceful` is switched to false it will force close any connections. This is called automatically after files are imported so typically *this method should never be required*.
49 changes: 32 additions & 17 deletions mysql-import.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

'use strict';

const mysql = require('mysql');
const mysql = require('mysql2');
const fs = require('fs');
const path = require("path");
const stream = require('stream');
Expand All @@ -31,8 +31,8 @@ class Importer{
this._conn = null;
this._encoding = 'utf8';
this._imported = [];
this._progressCB = ()=>{};
this._dumpCompletedCB = ()=>{};
this._progressCB = undefined;
this._dumpCompletedCB = undefined;
this._total_files = 0;
this._current_file_no = 0;
}
Expand Down Expand Up @@ -199,42 +199,53 @@ class Importer{
var parser = new queryParser({
db_connection: this._conn,
encoding: this._encoding,
onProgress: (progress) => {
onProgress: this._progressCB ? (progress) => {
this._progressCB({
total_files: this._total_files,
file_no: this._current_file_no,
bytes_processed: progress,
total_bytes: fileObj.size,
file_path: fileObj.file
});
}
} : undefined
});

const dumpCompletedCB = (err) => this._dumpCompletedCB({
const dumpCompletedCB = this._dumpCompletedCB ? (err) => this._dumpCompletedCB({
total_files: this._total_files,
file_no: this._current_file_no,
file_path: fileObj.file,
error: err
});
}) : undefined;

let completedCalled = false;

parser.on('finish', ()=>{
this._imported.push(fileObj.file);
dumpCompletedCB(null);
if (dumpCompletedCB && !completedCalled){
dumpCompletedCB(null);
completedCalled = true;
}
resolve();
});


parser.on('error', (err)=>{
dumpCompletedCB(err);
if (dumpCompletedCB && !completedCalled) {
dumpCompletedCB(err);
completedCalled = true;
}
reject(err);
});

var readerStream = fs.createReadStream(fileObj.file);
readerStream.setEncoding(this._encoding);

/* istanbul ignore next */
readerStream.on('error', (err)=>{
dumpCompletedCB(err);
if (dumpCompletedCB && !completedCalled) {
dumpCompletedCB(err);
completedCalled = true;
}
reject(err);
});

Expand Down Expand Up @@ -333,6 +344,7 @@ class Importer{
try{
await this._fileExists(filepath);
var stat = await this._statFile(filepath);
/* istanbul ignore else */
if(stat.isFile()){
if(filepath.toLowerCase().substring(filepath.length-4) === '.sql'){
full_paths.push({
Expand Down Expand Up @@ -409,10 +421,11 @@ class queryParser extends stream.Writable{
this.processed_size = 0;

// The progress callback
this.onProgress = options.onProgress || (() => {});
this.onProgress = options.onProgress;

// the encoding of the file being read
this.encoding = options.encoding || 'utf8';
// No default value anymore, since it is always provided by the parent caller.
this.encoding = options.encoding;

// the encoding of the database connection
this.db_connection = options.db_connection;
Expand Down Expand Up @@ -462,7 +475,9 @@ class queryParser extends stream.Writable{
}
}
this.processed_size += chunk.length;
this.onProgress(this.processed_size);
if (this.onProgress) {
this.onProgress(this.processed_size);
}
next(error);
}

Expand Down Expand Up @@ -550,12 +565,12 @@ class queryParser extends stream.Writable{
}

var query = false;
var demiliterFound = false;
var delimiterFound = false;
if(!this.quoteType && this.buffer.length >= this.delimiter.length){
demiliterFound = this.buffer.slice(-this.delimiter.length).join('') === this.delimiter;
delimiterFound = this.buffer.slice(-this.delimiter.length).join('') === this.delimiter;
}

if (demiliterFound) {
if (delimiterFound) {
// trim the delimiter off the end
this.buffer.splice(-this.delimiter.length, this.delimiter.length);
query = this.buffer.join('').trim();
Expand Down
Loading