better-sqlite3
Advanced tools
Comparing version 4.1.4 to 5.0.0
@@ -0,80 +1,79 @@ | ||
#!/usr/bin/env node | ||
'use strict'; | ||
const { spawn } = require('child_process'); | ||
const path = require('path'); | ||
const fs = require('fs-extra'); | ||
const { execFileSync } = require('child_process'); | ||
const clc = require('cli-color'); | ||
const factory = require('./factory'); | ||
process.chdir(path.dirname(__dirname)); | ||
process.on('SIGINT', exit); | ||
process.on('SIGHUP', exit); | ||
process.on('SIGTERM', exit); | ||
let trials; | ||
(function init() { | ||
fs.removeSync('temp/'); | ||
fs.ensureDirSync('temp/'); | ||
trials = getTrials(); | ||
if (!trials.length) { | ||
console.log(clc.yellow('No matching benchmarks found!')); | ||
return exit(); | ||
} | ||
console.log('Generating tables...'); | ||
factory.buildTables().then(() => { | ||
console.log(clc.magenta('--- Benchmarks ---')); | ||
nextTrial(); | ||
}, (err) => { | ||
console.log(clc.red(err && err.stack || err)); | ||
exit(1); | ||
}); | ||
}()); | ||
function getTrials() { | ||
const getTrials = (searchTerms) => { | ||
// Without any command-line arguments, we do a general-purpose benchmark. | ||
if (process.argv.length === 2) return require('./trials').default.map(addSearchTerms); | ||
if (!searchTerms.length) return require('./trials').default; | ||
// With command-line arguments, the user can run specific groups of trials. | ||
return process.argv.slice(2).reduce(filterByArgs, require('./trials').searchable.map(addSearchTerms)); | ||
function addSearchTerms(trial) { | ||
const size = trial.table.toLowerCase().includes('large') ? 'large' : 'small'; | ||
let columns = trial.columns.join(',').toLowerCase(); | ||
if (trial.columns.length > 1) columns = `(${columns})`; | ||
trial.terms = [trial.type.toLowerCase(), size, columns]; | ||
trial.looseTerms = (trial.pragma || []).filter(customPragma).join('; ').toLowerCase(); | ||
return trial; | ||
} | ||
function filterByArgs(trials, arg) { | ||
arg = arg.toLowerCase(); | ||
return trials.filter(obj => obj.terms.includes(arg) || obj.looseTerms.includes(arg)); | ||
} | ||
function customPragma(str) { | ||
return !str.includes('cache_size') && !str.includes('synchronous'); | ||
} | ||
return require('./trials').searchable.filter(filterBySearchTerms(searchTerms)); | ||
}; | ||
function exit(code) { | ||
fs.removeSync('temp/'); | ||
process.exit(typeof code === 'number' ? code : 0); | ||
const filterBySearchTerms = (searchTerms) => (trial) => { | ||
const terms = [ | ||
trial.type, | ||
trial.table, | ||
trial.table.replace('_empty', ''), | ||
`(${trial.columns.join(', ')})`, | ||
`(${trial.columns.join(',')})`, | ||
...trial.columns, | ||
...trial.customPragma, | ||
]; | ||
return searchTerms.every(arg => terms.includes(arg)); | ||
}; | ||
const displayTrialName = (trial) => { | ||
if (trial.description) return console.log(clc.magenta(`--- ${trial.description} ---`)); | ||
const name = `${trial.type} ${trial.table} (${trial.columns.join(', ')})`; | ||
const pragma = trial.customPragma.length ? ` | ${trial.customPragma.join('; ')}` : ''; | ||
console.log(clc.magenta(name) + clc.yellow(pragma)); | ||
}; | ||
const createContext = (trial, driver) => { | ||
const { data: _unused, ...tableInfo } = tables.get(trial.table); | ||
const ctx = { ...trial, ...tableInfo, driver, filename: `../temp/${iteration++}.db` }; | ||
return JSON.stringify(ctx); | ||
}; | ||
const erase = () => { | ||
return clc.move(0, -1) + clc.erase.line; | ||
}; | ||
// Determine which trials should be executed. | ||
process.chdir(__dirname); | ||
const trials = getTrials(process.argv.slice(2)); | ||
if (!trials.length) { | ||
console.log(clc.yellow('No matching benchmarks found!')); | ||
process.exit(); | ||
} | ||
function nextTrial() { | ||
if (!trials.length) { | ||
console.log(clc.green('All benchmarks complete!')); | ||
return exit(); | ||
// Create the temporary databases needed to run the benchmark trials. | ||
console.log('Generating tables...'); | ||
const drivers = require('./drivers'); | ||
const tables = require('./seed')(drivers.size * trials.length); | ||
process.stdout.write(erase()); | ||
// Execute each trial for each available driver. | ||
let iteration = 0; | ||
const nameLength = [...drivers.keys()].reduce((m, d) => Math.max(m, d.length), 0); | ||
for (const trial of trials) { | ||
displayTrialName(trial); | ||
for (const driver of drivers.keys()) { | ||
const driverName = driver.padEnd(nameLength); | ||
const ctx = createContext(trial, driver); | ||
process.stdout.write(`${driver} (running...)\n`); | ||
try { | ||
const result = execFileSync('./benchmark.js', [ctx], { stdio: 'pipe', encoding: 'utf8' }); | ||
console.log(erase() + `${driverName} x ${result}`); | ||
} catch (err) { | ||
console.log(erase() + clc.red(`${driverName} ERROR (probably out of memory)`)); | ||
process.stderr.write(clc.xterm(247)(clc.strip(err.stderr))); | ||
} | ||
} | ||
// Consume the next trial and display its name. | ||
const trial = trials.shift(); | ||
const extraName = trial.looseTerms ? clc.yellow(` | ${trial.looseTerms}`) : ''; | ||
console.log(clc.cyan(trial.terms.join(' ')) + extraName); | ||
// Spawn each trial within its own process. | ||
const child = spawn('node', [path.join(__dirname, 'types', trial.type), JSON.stringify(trial)], { stdio: 'inherit' }); | ||
child.on('exit', (code) => { | ||
if (code !== 0) console.log(clc.red('ERROR (probably out of memory)')); | ||
setTimeout(nextTrial, 0); | ||
}); | ||
console.log(''); | ||
} | ||
console.log(clc.green('All benchmarks complete!')); | ||
process.exit(); |
'use strict'; | ||
exports.default = [ | ||
{ type: 'select', table: 'allSmall', columns: ['integer', 'real', 'text', 'nul'] }, | ||
{ type: 'select-all', table: 'allSmall', columns: ['integer', 'real', 'text', 'nul'] }, | ||
{ type: 'select-iterate', table: 'allSmall', columns: ['integer', 'real', 'text', 'nul'] }, | ||
{ type: 'insert', table: 'allSmall', columns: ['integer', 'real', 'text', 'nul'], pragma: ['journal_mode = WAL', 'synchronous = 1'] }, | ||
{ type: 'insert', table: 'allSmall', columns: ['integer', 'real', 'text', 'nul'], pragma: ['journal_mode = DELETE', 'synchronous = 2'] }, | ||
{ type: 'transaction', table: 'allSmall', columns: ['integer', 'real', 'text', 'nul'] }, | ||
{ type: 'real-world', table: 'allSmall', columns: ['integer', 'real', 'text', 'nul'], pragma: ['journal_mode = WAL', 'synchronous = 1'] }, | ||
{ type: 'real-world', table: 'allSmall', columns: ['integer', 'real', 'text', 'nul'], pragma: ['journal_mode = DELETE', 'synchronous = 2'] }, | ||
{ type: 'select', table: 'small', columns: ['nul', 'integer', 'real', 'text'], | ||
description: 'reading rows individually' }, | ||
{ type: 'select-all', table: 'small', columns: ['nul', 'integer', 'real', 'text'], | ||
description: 'reading 100 rows into an array' }, | ||
{ type: 'select-iterate', table: 'small', columns: ['nul', 'integer', 'real', 'text'], | ||
description: 'iterating over 100 rows' }, | ||
{ type: 'insert', table: 'small_empty', columns: ['nul', 'integer', 'real', 'text'], | ||
description: 'inserting rows individually' }, | ||
{ type: 'transaction', table: 'small_empty', columns: ['nul', 'integer', 'real', 'text'], | ||
description: 'inserting 100 rows in a single transaction' }, | ||
]; | ||
exports.searchable = [ | ||
{ type: 'select', table: 'allSmall', columns: ['integer'] }, | ||
{ type: 'select', table: 'allSmall', columns: ['real'] }, | ||
{ type: 'select', table: 'allSmall', columns: ['text'] }, | ||
{ type: 'select', table: 'allSmall', columns: ['blob'] }, | ||
{ type: 'select', table: 'allSmall', columns: ['nul'] }, | ||
{ type: 'select', table: 'allLarge', columns: ['text'] }, | ||
{ type: 'select', table: 'allLarge', columns: ['blob'] }, | ||
{ type: 'select-all', table: 'allSmall', columns: ['integer'] }, | ||
{ type: 'select-all', table: 'allSmall', columns: ['real'] }, | ||
{ type: 'select-all', table: 'allSmall', columns: ['text'] }, | ||
{ type: 'select-all', table: 'allSmall', columns: ['blob'] }, | ||
{ type: 'select-all', table: 'allSmall', columns: ['nul'] }, | ||
{ type: 'select-all', table: 'allLarge', columns: ['text'] }, | ||
{ type: 'select-all', table: 'allLarge', columns: ['blob'] }, | ||
{ type: 'select-iterate', table: 'allSmall', columns: ['integer'] }, | ||
{ type: 'select-iterate', table: 'allSmall', columns: ['real'] }, | ||
{ type: 'select-iterate', table: 'allSmall', columns: ['text'] }, | ||
{ type: 'select-iterate', table: 'allSmall', columns: ['blob'] }, | ||
{ type: 'select-iterate', table: 'allSmall', columns: ['nul'] }, | ||
{ type: 'select-iterate', table: 'allLarge', columns: ['text'] }, | ||
{ type: 'select-iterate', table: 'allLarge', columns: ['blob'] }, | ||
{ type: 'insert', table: 'integerSmall', columns: ['integer'], pragma: ['journal_mode = WAL', 'synchronous = 1'] }, | ||
{ type: 'insert', table: 'realSmall', columns: ['real'], pragma: ['journal_mode = WAL', 'synchronous = 1'] }, | ||
{ type: 'insert', table: 'textSmall', columns: ['text'], pragma: ['journal_mode = WAL', 'synchronous = 1'] }, | ||
{ type: 'insert', table: 'blobSmall', columns: ['blob'], pragma: ['journal_mode = WAL', 'synchronous = 1'] }, | ||
{ type: 'insert', table: 'nulSmall', columns: ['nul'], pragma: ['journal_mode = WAL', 'synchronous = 1'] }, | ||
{ type: 'insert', table: 'textLarge', columns: ['text'], pragma: ['journal_mode = WAL', 'synchronous = 1'] }, | ||
{ type: 'insert', table: 'blobLarge', columns: ['blob'], pragma: ['journal_mode = WAL', 'synchronous = 1'] }, | ||
{ type: 'insert', table: 'integerSmall', columns: ['integer'], pragma: ['journal_mode = DELETE', 'synchronous = 2'] }, | ||
{ type: 'insert', table: 'realSmall', columns: ['real'], pragma: ['journal_mode = DELETE', 'synchronous = 2'] }, | ||
{ type: 'insert', table: 'textSmall', columns: ['text'], pragma: ['journal_mode = DELETE', 'synchronous = 2'] }, | ||
{ type: 'insert', table: 'blobSmall', columns: ['blob'], pragma: ['journal_mode = DELETE', 'synchronous = 2'] }, | ||
{ type: 'insert', table: 'nulSmall', columns: ['nul'], pragma: ['journal_mode = DELETE', 'synchronous = 2'] }, | ||
{ type: 'insert', table: 'textLarge', columns: ['text'], pragma: ['journal_mode = DELETE', 'synchronous = 2'] }, | ||
{ type: 'insert', table: 'blobLarge', columns: ['blob'], pragma: ['journal_mode = DELETE', 'synchronous = 2'] }, | ||
{ type: 'transaction', table: 'integerSmall', columns: ['integer'] }, | ||
{ type: 'transaction', table: 'realSmall', columns: ['real'] }, | ||
{ type: 'transaction', table: 'textSmall', columns: ['text'] }, | ||
{ type: 'transaction', table: 'blobSmall', columns: ['blob'] }, | ||
{ type: 'transaction', table: 'nulSmall', columns: ['nul'] }, | ||
{ type: 'transaction', table: 'textLarge', columns: ['text'] }, | ||
{ type: 'transaction', table: 'blobLarge', columns: ['blob'] }, | ||
{ type: 'select', table: 'small', columns: ['nul'] }, | ||
{ type: 'select', table: 'small', columns: ['integer'] }, | ||
{ type: 'select', table: 'small', columns: ['real'] }, | ||
{ type: 'select', table: 'small', columns: ['text'] }, | ||
{ type: 'select', table: 'small', columns: ['blob'] }, | ||
{ type: 'select', table: 'large', columns: ['text'] }, | ||
{ type: 'select', table: 'large', columns: ['blob'] }, | ||
{ type: 'select-all', table: 'small', columns: ['nul'] }, | ||
{ type: 'select-all', table: 'small', columns: ['integer'] }, | ||
{ type: 'select-all', table: 'small', columns: ['real'] }, | ||
{ type: 'select-all', table: 'small', columns: ['text'] }, | ||
{ type: 'select-all', table: 'small', columns: ['blob'] }, | ||
{ type: 'select-all', table: 'large', columns: ['text'] }, | ||
{ type: 'select-all', table: 'large', columns: ['blob'] }, | ||
{ type: 'select-iterate', table: 'small', columns: ['nul'] }, | ||
{ type: 'select-iterate', table: 'small', columns: ['integer'] }, | ||
{ type: 'select-iterate', table: 'small', columns: ['real'] }, | ||
{ type: 'select-iterate', table: 'small', columns: ['text'] }, | ||
{ type: 'select-iterate', table: 'small', columns: ['blob'] }, | ||
{ type: 'select-iterate', table: 'large', columns: ['text'] }, | ||
{ type: 'select-iterate', table: 'large', columns: ['blob'] }, | ||
{ type: 'insert', table: 'small_empty', columns: ['nul'] }, | ||
{ type: 'insert', table: 'small_empty', columns: ['integer'] }, | ||
{ type: 'insert', table: 'small_empty', columns: ['real'] }, | ||
{ type: 'insert', table: 'small_empty', columns: ['text'] }, | ||
{ type: 'insert', table: 'small_empty', columns: ['blob'] }, | ||
{ type: 'insert', table: 'large_empty', columns: ['text'] }, | ||
{ type: 'insert', table: 'large_empty', columns: ['blob'] }, | ||
{ type: 'transaction', table: 'small_empty', columns: ['nul'] }, | ||
{ type: 'transaction', table: 'small_empty', columns: ['integer'] }, | ||
{ type: 'transaction', table: 'small_empty', columns: ['real'] }, | ||
{ type: 'transaction', table: 'small_empty', columns: ['text'] }, | ||
{ type: 'transaction', table: 'small_empty', columns: ['blob'] }, | ||
{ type: 'transaction', table: 'large_empty', columns: ['text'] }, | ||
{ type: 'transaction', table: 'large_empty', columns: ['blob'] }, | ||
]; | ||
(() => { | ||
const cacheSize = /^(1|true|on|yes)$/i.test(process.env.NO_CACHE) ? 'cache_size = 0' : 'cache_size = -16000'; | ||
const trials = [].concat(...Object.keys(exports).map(key => exports[key])); | ||
for (const trial of trials) { | ||
trial.pragma = [cacheSize].concat(trial.pragma || []); | ||
const defaultPragma = []; | ||
const yes = /^\s*(1|true|on|yes)\s*$/i; | ||
if (yes.test(process.env.NO_CACHE)) defaultPragma.push('cache_size = 0'); | ||
else defaultPragma.push('cache_size = -16000'); | ||
if (yes.test(process.env.NO_WAL)) defaultPragma.push('journal_mode = DELETE', 'synchronous = FULL'); | ||
else defaultPragma.push('journal_mode = WAL', 'synchronous = NORMAL'); | ||
for (const trial of [].concat(...Object.values(exports))) { | ||
trial.customPragma = trial.pragma || []; | ||
trial.pragma = defaultPragma.concat(trial.customPragma); | ||
} | ||
})(); |
'use strict'; | ||
// Inserts 1 row | ||
require('../runner')((benchmark, dbs, ctx) => { | ||
const factory = require('../factory'); | ||
const SQL = `INSERT INTO ${ctx.table} (${ctx.columns.join(', ')}) VALUES ${factory.params(ctx.columns.length)}`; | ||
const betterSqlite3 = dbs['better-sqlite3']; | ||
const nodeSqlite3 = dbs['node-sqlite3']; | ||
const data = factory(ctx.table, ctx.columns); | ||
const betterSqlite3Insert = betterSqlite3.prepare(SQL); | ||
benchmark.add('better-sqlite3', () => { | ||
betterSqlite3Insert.run(data); | ||
}); | ||
benchmark.add('node-sqlite3', (deferred) => { | ||
nodeSqlite3.run(SQL, data).then(() => void deferred.resolve()); | ||
}); | ||
}); | ||
// Inserting rows individually (`.run()`) | ||
exports['better-sqlite3'] = (db, { table, columns }) => { | ||
const stmt = db.prepare(`INSERT INTO ${table} (${columns.join(', ')}) VALUES (${columns.map(x => '@' + x).join(', ')})`); | ||
const row = db.prepare(`SELECT * FROM ${table} LIMIT 1`).get(); | ||
return () => stmt.run(row); | ||
}; | ||
exports['node-sqlite3'] = async (db, { table, columns }) => { | ||
const sql = `INSERT INTO ${table} (${columns.join(', ')}) VALUES (${columns.map(x => '@' + x).join(', ')})`; | ||
const row = Object.assign({}, ...Object.entries(await db.get(`SELECT * FROM ${table} LIMIT 1`)) | ||
.filter(([k]) => columns.includes(k)) | ||
.map(([k, v]) => ({ ['@' + k]: v }))); | ||
return () => db.run(sql, row); | ||
}; |
'use strict'; | ||
// Selects 100 rows | ||
require('../runner')((benchmark, dbs, ctx) => { | ||
const SQL = `SELECT ${ctx.columns.join(', ')} FROM ${ctx.table} WHERE rowid>=? LIMIT 100`; | ||
const betterSqlite3 = dbs['better-sqlite3']; | ||
const nodeSqlite3 = dbs['node-sqlite3']; | ||
let rowid = 99; | ||
benchmark.on('cycle', () => { rowid = 99; }); | ||
const betterSqlite3Select = betterSqlite3.prepare(SQL); | ||
benchmark.add('better-sqlite3', () => { | ||
betterSqlite3Select.all(rowid - 98); | ||
rowid = (rowid + 100) % 1000; | ||
}); | ||
benchmark.add('node-sqlite3', (deferred) => { | ||
nodeSqlite3.all(SQL, rowid - 98).then(() => void deferred.resolve()); | ||
rowid = (rowid + 100) % 1000; | ||
}); | ||
}); | ||
// Reading 100 rows into an array (`.all()`) | ||
exports['better-sqlite3'] = (db, { table, columns, count }) => { | ||
const stmt = db.prepare(`SELECT ${columns.join(', ')} FROM ${table} WHERE rowid >= ? LIMIT 100`); | ||
let rowid = -100; | ||
return () => stmt.all((rowid += 100) % count + 1); | ||
}; | ||
exports['node-sqlite3'] = async (db, { table, columns, count }) => { | ||
const sql = `SELECT ${columns.join(', ')} FROM ${table} WHERE rowid >= ? LIMIT 100`; | ||
let rowid = -100; | ||
return () => db.all(sql, (rowid += 100) % count + 1); | ||
}; |
'use strict'; | ||
// Selects 100 rows | ||
require('../runner')((benchmark, dbs, ctx) => { | ||
const SQL = `SELECT ${ctx.columns.join(', ')} FROM ${ctx.table} WHERE rowid>=? LIMIT 100`; | ||
const oneByOneSQL = SQL.replace(/\bLIMIT\s+\d+/i, 'LIMIT 1'); | ||
const betterSqlite3 = dbs['better-sqlite3']; | ||
const nodeSqlite3 = dbs['node-sqlite3']; | ||
let rowid = 99; | ||
benchmark.on('cycle', () => { rowid = 99; }); | ||
const betterSqlite3Select = betterSqlite3.prepare(SQL); | ||
benchmark.add('better-sqlite3', () => { | ||
for (const obj of betterSqlite3Select.iterate(rowid - 98)) {} | ||
rowid = (rowid + 100) % 1000; | ||
}); | ||
benchmark.add('node-sqlite3', (deferred) => { | ||
let count = 0; | ||
function next() { | ||
if (++count === 100) { | ||
rowid = (rowid + 100) % 1000; | ||
deferred.resolve(); | ||
} else { | ||
nodeSqlite3.get(oneByOneSQL, rowid + count - 98).then(next); | ||
} | ||
} | ||
nodeSqlite3.get(oneByOneSQL, rowid + count - 98).then(next); | ||
}); | ||
}); | ||
// Iterating over 100 rows (`.iterate()`) | ||
exports['better-sqlite3'] = (db, { table, columns, count }) => { | ||
const stmt = db.prepare(`SELECT ${columns.join(', ')} FROM ${table} WHERE rowid >= ? LIMIT 100`); | ||
let rowid = -100; | ||
return () => { | ||
for (const row of stmt.iterate((rowid += 100) % count + 1)) {} | ||
}; | ||
}; | ||
exports['node-sqlite3'] = async (db, { table, columns, count }) => { | ||
const sql = `SELECT ${columns.join(', ')} FROM ${table} WHERE rowid = ?`; | ||
let rowid = -100; | ||
return () => { | ||
rowid += 100; | ||
let index = 0; | ||
return (function next() { | ||
if (index === 100) return; | ||
return db.get(sql, (rowid + index++) % count + 1).then(next); | ||
})(); | ||
}; | ||
}; |
'use strict'; | ||
// Selects 1 row | ||
require('../runner')((benchmark, dbs, ctx) => { | ||
const SQL = `SELECT ${ctx.columns.join(', ')} FROM ${ctx.table} WHERE rowid=?`; | ||
const betterSqlite3 = dbs['better-sqlite3']; | ||
const nodeSqlite3 = dbs['node-sqlite3']; | ||
let rowid = 0; | ||
benchmark.on('cycle', () => { rowid = 0; }); | ||
const betterSqlite3Select = betterSqlite3.prepare(SQL); | ||
benchmark.add('better-sqlite3', () => { | ||
betterSqlite3Select.get(rowid + 1); | ||
rowid = (rowid + 1) % 1000; | ||
}); | ||
benchmark.add('node-sqlite3', (deferred) => { | ||
nodeSqlite3.get(SQL, rowid + 1).then(() => void deferred.resolve()); | ||
rowid = (rowid + 1) % 1000; | ||
}); | ||
}); | ||
// Reading rows individually (`.get()`) | ||
exports['better-sqlite3'] = (db, { table, columns, count }) => { | ||
const stmt = db.prepare(`SELECT ${columns.join(', ')} FROM ${table} WHERE rowid = ?`); | ||
let rowid = -1; | ||
return () => stmt.get(++rowid % count + 1); | ||
}; | ||
exports['node-sqlite3'] = async (db, { table, columns, count }) => { | ||
const sql = `SELECT ${columns.join(', ')} FROM ${table} WHERE rowid = ?`; | ||
let rowid = -1; | ||
return () => db.get(sql, ++rowid % count + 1); | ||
}; |
'use strict'; | ||
// Inserts 100 rows | ||
require('../runner')((benchmark, dbs, ctx) => { | ||
const SQL = `INSERT INTO ${ctx.table} (${ctx.columns.join(', ')}) VALUES (${namedParams(ctx.columns).join(', ')})`; | ||
const betterSqlite3 = dbs['better-sqlite3']; | ||
const nodeSqlite3 = dbs['node-sqlite3']; | ||
const data = namedData(ctx.table, ctx.columns); | ||
const dataWithPrefix = namedData(ctx.table, ctx.columns, true); | ||
const betterSqlite3Transaction = betterSqlite3.transaction(new Array(100).fill(SQL)); | ||
benchmark.add('better-sqlite3', () => { | ||
betterSqlite3Transaction.run(data); | ||
// Inserting 100 rows in a single transaction | ||
exports['better-sqlite3'] = (db, { table, columns }) => { | ||
const stmt = db.prepare(`INSERT INTO ${table} (${columns.join(', ')}) VALUES (${columns.map(x => '@' + x).join(', ')})`); | ||
const row = db.prepare(`SELECT * FROM ${table} LIMIT 1`).get(); | ||
const trx = db.transaction((row) => { | ||
for (let i = 0; i < 100; ++i) stmt.run(row); | ||
}); | ||
benchmark.add('node-sqlite3', (deferred) => { | ||
let count = 0; | ||
nodeSqlite3.run('BEGIN').then(function insert() { | ||
if (++count < 100) { | ||
return nodeSqlite3.run(SQL, dataWithPrefix).then(insert); | ||
} else { | ||
return nodeSqlite3.run(SQL, dataWithPrefix).then(() => { | ||
return nodeSqlite3.run('COMMIT').then(() => void deferred.resolve()); | ||
}); | ||
return () => trx(row); | ||
}; | ||
exports['node-sqlite3'] = async (db, { table, columns, driver, filename, pragma }) => { | ||
const sql = `INSERT INTO ${table} (${columns.join(', ')}) VALUES (${columns.map(x => '@' + x).join(', ')})`; | ||
const row = Object.assign({}, ...Object.entries(await db.get(`SELECT * FROM ${table} LIMIT 1`)) | ||
.filter(([k]) => columns.includes(k)) | ||
.map(([k, v]) => ({ ['@' + k]: v }))); | ||
const open = require('../drivers').get(driver); | ||
/* | ||
The only way to create an isolated transaction with node-sqlite3 in a | ||
random-access environment (i.e., a web server) is to open a new database | ||
connection for each transaction. | ||
(http://github.com/mapbox/node-sqlite3/issues/304#issuecomment-45242331) | ||
*/ | ||
return () => open(filename, pragma).then(async (db) => { | ||
try { | ||
await db.run('BEGIN'); | ||
try { | ||
for (let i = 0; i < 100; ++i) await db.run(sql, row); | ||
await db.run('COMMIT'); | ||
} catch (err) { | ||
try { await db.run('ROLLBACK'); } | ||
catch (_) { /* this is necessary because there's no db.inTransaction property */ } | ||
throw err; | ||
} | ||
}); | ||
} finally { | ||
await db.close(); | ||
} | ||
}); | ||
}); | ||
function namedParams(columns) { | ||
return columns.map((_, i) => '@x' + i); | ||
} | ||
function namedData(table, columns, withPrefix) { | ||
const data = require('../factory')(table, columns); | ||
const bindNames = namedParams(columns); | ||
const wrappedData = data.map((item, i) => ({ [bindNames[i].slice(+!withPrefix)]: item })); | ||
return Object.assign({}, ...wrappedData); | ||
} | ||
}; |
'use strict'; | ||
const fs = require('fs'); | ||
const path = require('path'); | ||
const util = require('./util'); | ||
const CPPDatabase = require('bindings')({ | ||
bindings: 'better_sqlite3.node', | ||
module_root: path.resolve(__dirname, '..'), | ||
}).Database; | ||
const CPPDatabase = require('../build/better_sqlite3.node').Database; | ||
function Database(filenameGiven, options) { | ||
if (typeof filenameGiven !== 'string') { | ||
throw new TypeError('Expected first argument to be a string'); | ||
} | ||
if (options == null) options = {}; | ||
if (typeof filenameGiven !== 'string') throw new TypeError('Expected first argument to be a string'); | ||
if (typeof options !== 'object') throw new TypeError('Expected second argument to be an options object'); | ||
let filename = filenameGiven.trim(); | ||
if (!filename) { | ||
throw new TypeError('A database filename cannot be an empty string'); | ||
} | ||
if (/^file:/i.test(filename)) { | ||
throw new TypeError('URI filenames are reserved for internal use only'); | ||
} | ||
if (/^(:memory:)?$/i.test(filename)) { | ||
throw new TypeError('To create an in-memory database, specify a normal filename and use the "memory" option'); | ||
} | ||
if (!filename) throw new TypeError('Database filename cannot be an empty string'); | ||
if (filename.toLowerCase().startsWith('file:')) throw new TypeError('URI filenames are reserved for internal use only'); | ||
if ('readOnly' in options) throw new TypeError('Misspelled option "readOnly" should be "readonly"'); | ||
if (typeof options !== 'object' || options === null) { | ||
options = {}; | ||
} | ||
if ('readOnly' in options) { | ||
throw new TypeError('Misspelled option "readOnly" should be "readonly"'); | ||
} | ||
const anonymous = filename === ':memory:'; | ||
const memory = util.getBooleanOption(options, 'memory'); | ||
const readonly = util.getBooleanOption(options, 'readonly'); | ||
const fileMustExist = util.getBooleanOption(options, 'fileMustExist'); | ||
const timeout = 'timeout' in options ? options.timeout : 5000; | ||
if (memory) { | ||
if (readonly && (memory || anonymous)) throw new TypeError('In-memory databases cannot be readonly'); | ||
if (anonymous && !memory && 'memory' in options) throw new TypeError('Option "memory" conflicts with :memory: filename'); | ||
if (!Number.isInteger(timeout) || timeout < 0) throw new TypeError('Expected the "timeout" option to be a positive integer'); | ||
if (timeout > 0x7fffffff) throw new RangeError('Option "timeout" cannot be greater than 2147483647'); | ||
if (!memory && !anonymous && !fs.existsSync(path.dirname(filename))) { | ||
throw new TypeError('Cannot open database because the directory does not exist'); | ||
} | ||
if (memory && !anonymous) { | ||
if (process.platform === 'win32') { | ||
@@ -40,17 +38,16 @@ filename = filename.replace(/\\/g, '/').replace(/^[a-z]:\//i, '/$&'); | ||
+ filename | ||
.replace(/#/g, '%23') | ||
.replace(/\?/g, '%3f') | ||
.replace(/\/\/+/g, '/') | ||
.replace(/#/g, '%23') | ||
+ '?mode=memory&cache=shared'; | ||
} else if (!util.pathExists(path.dirname(filename))) { | ||
throw new TypeError('Cannot open database because the directory does not exist'); | ||
} | ||
return new CPPDatabase(filename, filenameGiven, memory, readonly, fileMustExist); | ||
return new CPPDatabase(filename, filenameGiven, memory || anonymous, readonly, fileMustExist, timeout); | ||
} | ||
util.wrap(CPPDatabase, 'pragma', require('./pragma')); | ||
util.wrap(CPPDatabase, 'register', require('./register')); | ||
util.wrap(CPPDatabase, 'function', require('./function')); | ||
util.wrap(CPPDatabase, 'aggregate', require('./aggregate')); | ||
CPPDatabase.prototype.transaction = require('./transaction'); | ||
CPPDatabase.prototype.constructor = Database; | ||
Database.prototype = CPPDatabase.prototype; | ||
module.exports = Database; |
'use strict'; | ||
const { getBooleanOption } = require('./util'); | ||
module.exports = (setPragmaMode) => { | ||
return function pragma(source, simplify) { | ||
if (typeof source !== 'string') { | ||
throw new TypeError('Expected first argument to be a string'); | ||
} | ||
if (arguments.length > 1 && typeof simplify !== 'boolean') { | ||
throw new TypeError('Expected second argument to be a boolean'); | ||
} | ||
return function pragma(source, options) { | ||
if (options == null) options = {}; | ||
if (typeof source !== 'string') throw new TypeError('Expected first argument to be a string'); | ||
if (typeof options !== 'object') throw new TypeError('Expected second argument to be an options object'); | ||
const simple = getBooleanOption(options, 'simple'); | ||
setPragmaMode.call(this, true); | ||
try { | ||
return simplify | ||
return simple | ||
? this.prepare(`PRAGMA ${source}`).pluck().get() | ||
@@ -16,0 +15,0 @@ : this.prepare(`PRAGMA ${source}`).all(); |
@@ -18,4 +18,5 @@ 'use strict'; | ||
} | ||
Object.setPrototypeOf(SqliteError, Error); | ||
Object.setPrototypeOf(SqliteError.prototype, Error.prototype); | ||
Object.defineProperty(SqliteError.prototype, 'name', descriptor); | ||
module.exports = SqliteError; |
'use strict'; | ||
const fs = require('fs'); | ||
@@ -12,7 +11,2 @@ exports.getBooleanOption = (options, key) => { | ||
exports.pathExists = (path) => { | ||
try { fs.accessSync(path); return true; } | ||
catch (_) { return false; } | ||
}; | ||
exports.wrap = (Class, methodName, wrapper) => { | ||
@@ -19,0 +13,0 @@ const originalMethod = Class.prototype[methodName]; |
{ | ||
"name": "better-sqlite3", | ||
"version": "4.1.4", | ||
"version": "5.0.0", | ||
"description": "The fastest and simplest library for SQLite3 in Node.js.", | ||
"homepage": "http://github.com/JoshuaWise/better-sqlite3", | ||
"author": "Joshua Wise <joshuathomaswise@gmail.com>", | ||
"main": "lib/index.js", | ||
"repository": { | ||
@@ -12,14 +13,15 @@ "type": "git", | ||
"dependencies": { | ||
"bindings": "^1.3.0", | ||
"integer": "^1.0.5" | ||
"integer": "^2.1.0", | ||
"tar": "^4.4.6" | ||
}, | ||
"devDependencies": { | ||
"benchmark": "^2.1.4", | ||
"chai": "^4.1.2", | ||
"cli-color": "^1.2.0", | ||
"chai": "^4.2.0", | ||
"cli-color": "^1.3.0", | ||
"fs-extra": "^5.0.0", | ||
"mocha": "^4.1.0", | ||
"sqlite": "^2.9.0" | ||
"mocha": "^5.2.0", | ||
"nodemark": "^0.3.0", | ||
"sqlite": "^2.9.3" | ||
}, | ||
"scripts": { | ||
"download": "sh ./deps/download.sh", | ||
"lzz": "lzz -hx hpp -sx cpp -k BETTER_SQLITE3 -d -hl -sl -e ./src/better_sqlite3.lzz", | ||
@@ -30,5 +32,3 @@ "prepublishOnly": "npm run lzz", | ||
"rebuild-debug": "npm run lzz && node-gyp rebuild --debug", | ||
"test": "$(npm bin)/mocha --bail --timeout 5000 --slow 5000", | ||
"pretest": "rm -r ./temp/ || true && mkdir ./temp/", | ||
"posttest": "rm -r ./temp/", | ||
"test": "$(npm bin)/mocha --exit --slow=75 --timeout=2000", | ||
"benchmark": "node benchmark" | ||
@@ -41,7 +41,8 @@ }, | ||
"sqlite3", | ||
"custom", | ||
"aggregate", | ||
"database", | ||
"transactions" | ||
"transactions", | ||
"user-defined functions", | ||
"aggregate functions", | ||
"window functions", | ||
"database" | ||
] | ||
} |
'use strict'; | ||
const { expect } = require('chai'); | ||
const fs = require('fs'); | ||
const { existsSync } = require('fs'); | ||
const Database = require('../.'); | ||
const util = require('./util'); | ||
describe('new Database()', function () { | ||
it('should throw an exception when file path is not a string', function () { | ||
it('should throw when file path is not a string', function () { | ||
expect(() => new Database()).to.throw(TypeError); | ||
@@ -14,17 +12,21 @@ expect(() => new Database(null)).to.throw(TypeError); | ||
expect(() => new Database(new String(util.next()))).to.throw(TypeError); | ||
expect(() => new Database(() => {})).to.throw(TypeError); | ||
expect(() => new Database(() => util.next())).to.throw(TypeError); | ||
expect(() => new Database([util.next()])).to.throw(TypeError); | ||
}); | ||
it('should throw an exception when file path is empty', function () { | ||
it('should throw when file path is empty', function () { | ||
expect(() => new Database('')).to.throw(TypeError); | ||
}); | ||
it('should throw when boolean options are provided as non-booleans', function () { | ||
expect(() => new Database(util.next(), { readOnly: false })).to.throw(TypeError); | ||
expect(() => new Database(util.next(), { readonly: undefined })).to.throw(TypeError); | ||
expect(() => new Database(util.next(), { memory: undefined })).to.throw(TypeError); | ||
expect(() => new Database(util.next(), { fileMustExist: undefined })).to.throw(TypeError); | ||
}); | ||
it('should not allow URI file paths', function () { | ||
expect(() => new Database(`file: ${util.next()}`)).to.throw(TypeError); | ||
expect(() => new Database(`file: ${util.next()}?mode=memory&cache=shared`)).to.throw(TypeError); | ||
expect(() => new Database(`FILE:${util.next()}`)).to.throw(TypeError); | ||
expect(() => new Database(`file:${util.next()}`)).to.throw(TypeError); | ||
expect(() => new Database(`file:${util.next()}?mode=memory&cache=shared`)).to.throw(TypeError); | ||
}); | ||
it('should not allow ":memory:" databases', function () { | ||
expect(() => new Database(':memory:')).to.throw(TypeError); | ||
}); | ||
it('should allow disk-based databases to be created', function () { | ||
expect(() => fs.accessSync(util.next())).to.throw(Error); | ||
it('should allow disk-bound databases to be created', function () { | ||
expect(existsSync(util.next())).to.be.false; | ||
const db = Database(util.current()); | ||
@@ -36,6 +38,6 @@ expect(db.name).to.equal(util.current()); | ||
expect(db.inTransaction).to.be.false; | ||
fs.accessSync(util.current()); | ||
expect(existsSync(util.current())).to.be.true; | ||
}); | ||
it('should allow in-memory databases to be created', function () { | ||
expect(() => fs.accessSync(util.next())).to.throw(Error); | ||
it('should allow named in-memory databases to be created', function () { | ||
expect(existsSync(util.next())).to.be.false; | ||
const db = new Database(util.current(), { memory: true }); | ||
@@ -47,9 +49,22 @@ expect(db.name).to.equal(util.current()); | ||
expect(db.inTransaction).to.be.false; | ||
expect(() => fs.accessSync(util.current())).to.throw(Error); | ||
expect(existsSync(util.current())).to.be.false; | ||
}); | ||
it('should allow anonymous in-memory databases to be created', function () { | ||
const db = new Database(':memory:'); | ||
expect(db.name).to.equal(':memory:'); | ||
expect(db.memory).to.be.true; | ||
expect(db.readonly).to.be.false; | ||
expect(db.open).to.be.true; | ||
expect(db.inTransaction).to.be.false; | ||
expect(existsSync(':memory:')).to.be.false; | ||
}); | ||
it('should not allow conflicting in-memory options', function () { | ||
expect(() => new Database(':memory:', { memory: false })).to.throw(TypeError); | ||
(new Database(':memory:', { memory: true })).close(); | ||
}); | ||
it('should allow readonly database connections to be created', function () { | ||
expect(() => fs.accessSync(util.next())).to.throw(Error); | ||
expect(existsSync(util.next())).to.be.false; | ||
expect(() => new Database(util.current(), { readonly: true })).to.throw(Database.SqliteError).with.property('code', 'SQLITE_CANTOPEN'); | ||
(new Database(util.current())).close(); | ||
fs.accessSync(util.current()); | ||
expect(existsSync(util.current())).to.be.true; | ||
const db = new Database(util.current(), { readonly: true }); | ||
@@ -61,19 +76,15 @@ expect(db.name).to.equal(util.current()); | ||
expect(db.inTransaction).to.be.false; | ||
fs.accessSync(util.current()); | ||
expect(existsSync(util.current())).to.be.true; | ||
}); | ||
it('should allow the "readonly" and "memory" options on the same connection', function () { | ||
expect(() => fs.accessSync(util.next())).to.throw(Error); | ||
const db = new Database(util.current(), { memory: true, readonly: true }); | ||
expect(db.name).to.equal(util.current()); | ||
expect(db.memory).to.be.true; | ||
expect(db.readonly).to.be.true; | ||
expect(db.open).to.be.true; | ||
expect(db.inTransaction).to.be.false; | ||
expect(() => fs.accessSync(util.current())).to.throw(Error); | ||
it('should not allow the "readonly" option for in-memory databases', function () { | ||
expect(existsSync(util.next())).to.be.false; | ||
expect(() => new Database(util.current(), { memory: true, readonly: true })).to.throw(TypeError); | ||
expect(() => new Database(':memory:', { readonly: true })).to.throw(TypeError); | ||
expect(existsSync(util.current())).to.be.false; | ||
}); | ||
it('should accept the "fileMustExist" option', function () { | ||
expect(() => fs.accessSync(util.next())).to.throw(Error); | ||
expect(existsSync(util.next())).to.be.false; | ||
expect(() => new Database(util.current(), { fileMustExist: true })).to.throw(Database.SqliteError).with.property('code', 'SQLITE_CANTOPEN'); | ||
(new Database(util.current())).close(); | ||
fs.accessSync(util.current()); | ||
expect(existsSync(util.current())).to.be.true; | ||
const db = new Database(util.current(), { fileMustExist: true }); | ||
@@ -85,19 +96,32 @@ expect(db.name).to.equal(util.current()); | ||
expect(db.inTransaction).to.be.false; | ||
fs.accessSync(util.current()); | ||
expect(existsSync(util.current())).to.be.true; | ||
}); | ||
it('should ignore "fileMustExist" when the "memory" option is true', function () { | ||
expect(() => fs.accessSync(util.next())).to.throw(Error); | ||
const db = new Database(util.current(), { memory: true, fileMustExist: true }); | ||
expect(db.name).to.equal(util.current()); | ||
expect(db.memory).to.be.true; | ||
expect(db.readonly).to.be.false; | ||
expect(db.open).to.be.true; | ||
expect(db.inTransaction).to.be.false; | ||
expect(() => fs.accessSync(util.current())).to.throw(Error); | ||
it('should accept the "timeout" option', function () { | ||
this.slow(2500); | ||
const testTimeout = (timeout) => { | ||
const db = new Database(util.current(), { timeout }); | ||
const start = Date.now(); | ||
expect(() => db.exec('BEGIN EXCLUSIVE')).to.throw(Database.SqliteError).with.property('code', 'SQLITE_BUSY'); | ||
const end = Date.now(); | ||
expect(end - start).to.be.within(timeout - 100, timeout + 100); | ||
db.close(); | ||
}; | ||
const blocker = new Database(util.next(), { timeout: 0x7fffffff }); | ||
blocker.exec('BEGIN EXCLUSIVE'); | ||
testTimeout(0); | ||
testTimeout(1000); | ||
blocker.close(); | ||
expect(() => new Database(util.current(), { timeout: undefined })).to.throw(TypeError); | ||
expect(() => new Database(util.current(), { timeout: null })).to.throw(TypeError); | ||
expect(() => new Database(util.current(), { timeout: NaN })).to.throw(TypeError); | ||
expect(() => new Database(util.current(), { timeout: '75' })).to.throw(TypeError); | ||
expect(() => new Database(util.current(), { timeout: -1 })).to.throw(TypeError); | ||
expect(() => new Database(util.current(), { timeout: 75.01 })).to.throw(TypeError); | ||
expect(() => new Database(util.current(), { timeout: 0x80000000 })).to.throw(RangeError); | ||
}); | ||
it('should throw an Error if opening the database failed', function () { | ||
expect(() => fs.accessSync(util.next())).to.throw(Error); | ||
expect(existsSync(util.next())).to.be.false; | ||
expect(() => new Database(`temp/nonexistent/abcfoobar123/${util.current()}`)).to.throw(TypeError); | ||
expect(() => fs.accessSync(util.current())).to.throw(Error); | ||
}) | ||
expect(existsSync(util.current())).to.be.false; | ||
}); | ||
it('should have a proper prototype chain', function () { | ||
@@ -104,0 +128,0 @@ const db = new Database(util.next()); |
'use strict'; | ||
const { expect } = require('chai'); | ||
const { existsSync } = require('fs'); | ||
const Database = require('../.'); | ||
const util = require('./util'); | ||
describe('Database#close()', function () { | ||
beforeEach(function () { | ||
this.db = new Database(util.next()); | ||
}); | ||
afterEach(function () { | ||
this.db.close(); | ||
}); | ||
it('should cause db.open to return false', function () { | ||
const db = new Database(util.next()); | ||
expect(db.open).to.be.true; | ||
db.close(); | ||
expect(db.open).to.be.false; | ||
expect(this.db.open).to.be.true; | ||
this.db.close(); | ||
expect(this.db.open).to.be.false; | ||
}); | ||
it('should return the database object', function () { | ||
const db = new Database(util.next()); | ||
expect(db.open).to.be.true; | ||
expect(db.close()).to.equal(db); | ||
expect(db.open).to.be.false; | ||
expect(db.close()).to.equal(db); | ||
expect(db.open).to.be.false; | ||
expect(this.db.open).to.be.true; | ||
expect(this.db.close()).to.equal(this.db); | ||
expect(this.db.open).to.be.false; | ||
expect(this.db.close()).to.equal(this.db); | ||
expect(this.db.open).to.be.false; | ||
}); | ||
it('should prevent any further database operations', function () { | ||
const db = new Database(util.next()); | ||
db.close(); | ||
expect(() => db.prepare('CREATE TABLE people (name TEXT)')).to.throw(TypeError); | ||
expect(() => db.transaction(['CREATE TABLE people (name TEXT)'])).to.throw(TypeError); | ||
expect(() => db.pragma('cache_size')).to.throw(TypeError); | ||
expect(() => db.checkpoint()).to.throw(TypeError); | ||
this.db.close(); | ||
expect(() => this.db.exec('CREATE TABLE people (name TEXT)')).to.throw(TypeError); | ||
expect(() => this.db.prepare('CREATE TABLE cats (name TEXT)')).to.throw(TypeError); | ||
expect(() => this.db.transaction(() => {})).to.throw(TypeError); | ||
expect(() => this.db.pragma('cache_size')).to.throw(TypeError); | ||
expect(() => this.db.checkpoint()).to.throw(TypeError); | ||
expect(() => this.db.function('foo', () => {})).to.throw(TypeError); | ||
expect(() => this.db.aggregate('foo', { step: () => {} })).to.throw(TypeError); | ||
}); | ||
it('should prevent any existing statements from running', function () { | ||
this.db.prepare('CREATE TABLE people (name TEXT)').run(); | ||
const stmt1 = this.db.prepare('SELECT * FROM people'); | ||
const stmt2 = this.db.prepare("INSERT INTO people VALUES ('foobar')"); | ||
this.db.prepare('SELECT * FROM people').bind(); | ||
this.db.prepare("INSERT INTO people VALUES ('foobar')").bind(); | ||
this.db.prepare('SELECT * FROM people').get(); | ||
this.db.prepare('SELECT * FROM people').all(); | ||
this.db.prepare('SELECT * FROM people').iterate().return(); | ||
this.db.prepare("INSERT INTO people VALUES ('foobar')").run(); | ||
this.db.close(); | ||
expect(() => stmt1.bind()).to.throw(TypeError); | ||
expect(() => stmt2.bind()).to.throw(TypeError); | ||
expect(() => stmt1.get()).to.throw(TypeError); | ||
expect(() => stmt1.all()).to.throw(TypeError); | ||
expect(() => stmt1.iterate()).to.throw(TypeError); | ||
expect(() => stmt2.run()).to.throw(TypeError); | ||
}); | ||
it('should delete the database\'s associated temporary files', function () { | ||
expect(existsSync(util.current())).to.be.true; | ||
this.db.pragma('journal_mode = WAL'); | ||
this.db.prepare('CREATE TABLE people (name TEXT)').run(); | ||
this.db.prepare('INSERT INTO people VALUES (?)').run('foobar'); | ||
expect(existsSync(`${util.current()}-wal`)).to.be.true; | ||
this.db.close(); | ||
expect(existsSync(util.current())).to.be.true; | ||
expect(existsSync(`${util.current()}-wal`)).to.be.false; | ||
}); | ||
}); |
'use strict'; | ||
const { expect } = require('chai'); | ||
const Database = require('../.'); | ||
const util = require('./util'); | ||
describe('Database#pragma()', function () { | ||
beforeEach(function () { | ||
this.db = new Database(util.next()); | ||
}); | ||
afterEach(function () { | ||
this.db.close(); | ||
}); | ||
it('should throw an exception if a string is not provided', function () { | ||
const db = new Database(util.next()); | ||
expect(() => db.pragma(123)).to.throw(TypeError); | ||
expect(() => db.pragma(0)).to.throw(TypeError); | ||
expect(() => db.pragma(null)).to.throw(TypeError); | ||
expect(() => db.pragma()).to.throw(TypeError); | ||
expect(() => db.pragma(new String('cache_size'))).to.throw(TypeError); | ||
expect(() => this.db.pragma(123)).to.throw(TypeError); | ||
expect(() => this.db.pragma(0)).to.throw(TypeError); | ||
expect(() => this.db.pragma(null)).to.throw(TypeError); | ||
expect(() => this.db.pragma()).to.throw(TypeError); | ||
expect(() => this.db.pragma(new String('cache_size'))).to.throw(TypeError); | ||
}); | ||
it('should throw an exception if boolean options are provided as non-booleans', function () { | ||
expect(() => this.db.pragma('cache_size', { simple: undefined })).to.throw(TypeError); | ||
}); | ||
it('should throw an exception if invalid/redundant SQL is provided', function () { | ||
const db = new Database(util.next()); | ||
expect(() => db.pragma('PRAGMA cache_size')).to.throw(Database.SqliteError).with.property('code', 'SQLITE_ERROR'); | ||
expect(() => db.pragma('cache_size; PRAGMA cache_size')).to.throw(RangeError); | ||
expect(() => this.db.pragma('PRAGMA cache_size')).to.throw(Database.SqliteError).with.property('code', 'SQLITE_ERROR'); | ||
expect(() => this.db.pragma('cache_size; PRAGMA cache_size')).to.throw(RangeError); | ||
}); | ||
it('should execute the pragma, returning rows of results', function () { | ||
const db = new Database(util.next()); | ||
const rows = db.pragma('cache_size'); | ||
const rows = this.db.pragma('cache_size'); | ||
expect(rows).to.be.an('array'); | ||
expect(rows[0]).to.be.an('object'); | ||
expect(rows[0].cache_size).to.be.a('number'); | ||
@@ -27,36 +34,33 @@ expect(rows[0].cache_size).to.equal(-16000); | ||
it('should optionally return simpler results', function () { | ||
const db = new Database(util.next()); | ||
const cache_size = db.pragma('cache_size', true); | ||
expect(this.db.pragma('cache_size', { simple: false })).to.be.an('array'); | ||
const cache_size = this.db.pragma('cache_size', { simple: true }); | ||
expect(cache_size).to.be.a('number'); | ||
expect(cache_size).to.equal(-16000); | ||
expect(() => db.pragma('cache_size', undefined)).to.throw(TypeError); | ||
expect(() => db.pragma('cache_size', null)).to.throw(TypeError); | ||
expect(() => db.pragma('cache_size', 123)).to.throw(TypeError); | ||
expect(() => db.pragma('cache_size', function () {})).to.throw(TypeError); | ||
expect(() => db.pragma('cache_size', NaN)).to.throw(TypeError); | ||
expect(() => db.pragma('cache_size', 'true')).to.throw(TypeError); | ||
expect(() => this.db.pragma('cache_size', true)).to.throw(TypeError); | ||
expect(() => this.db.pragma('cache_size', 123)).to.throw(TypeError); | ||
expect(() => this.db.pragma('cache_size', function () {})).to.throw(TypeError); | ||
expect(() => this.db.pragma('cache_size', NaN)).to.throw(TypeError); | ||
expect(() => this.db.pragma('cache_size', 'true')).to.throw(TypeError); | ||
}); | ||
it('should obey PRAGMA changes', function () { | ||
const db = new Database(util.next()); | ||
expect(db.pragma('cache_size', true)).to.equal(-16000); | ||
db.pragma('cache_size = -8000'); | ||
expect(db.pragma('cache_size', true)).to.equal(-8000); | ||
expect(db.pragma('journal_mode', true)).to.equal('delete'); | ||
db.pragma('journal_mode = wal'); | ||
expect(db.pragma('journal_mode', true)).to.equal('wal'); | ||
expect(this.db.pragma('cache_size', { simple: true })).to.equal(-16000); | ||
this.db.pragma('cache_size = -8000'); | ||
expect(this.db.pragma('cache_size', { simple: true })).to.equal(-8000); | ||
expect(this.db.pragma('journal_mode', { simple: true })).to.equal('delete'); | ||
this.db.pragma('journal_mode = wal'); | ||
expect(this.db.pragma('journal_mode', { simple: true })).to.equal('wal'); | ||
}); | ||
it('should respect readonly connections', function () { | ||
(new Database(util.next())).close(); | ||
const db = new Database(util.current(), { readonly: true, fileMustExist: true }); | ||
expect(db.pragma('cache_size', true)).to.equal(-16000); | ||
db.pragma('cache_size = -8000'); | ||
expect(db.pragma('cache_size', true)).to.equal(-8000); | ||
expect(db.pragma('journal_mode', true)).to.equal('delete'); | ||
expect(() => db.pragma('journal_mode = wal')).to.throw(Database.SqliteError).with.property('code', 'SQLITE_READONLY'); | ||
expect(db.pragma('journal_mode', true)).to.equal('delete'); | ||
this.db.close(); | ||
this.db = new Database(util.current(), { readonly: true, fileMustExist: true }); | ||
expect(this.db.pragma('cache_size', { simple: true })).to.equal(-16000); | ||
this.db.pragma('cache_size = -8000'); | ||
expect(this.db.pragma('cache_size', { simple: true })).to.equal(-8000); | ||
expect(this.db.pragma('journal_mode', { simple: true })).to.equal('delete'); | ||
expect(() => this.db.pragma('journal_mode = wal')).to.throw(Database.SqliteError).with.property('code', 'SQLITE_READONLY'); | ||
expect(this.db.pragma('journal_mode', { simple: true })).to.equal('delete'); | ||
}); | ||
it('should return undefined if no rows exist and simpler results are desired', function () { | ||
const db = new Database(util.next()); | ||
expect(db.pragma('table_info', true)).to.be.undefined; | ||
expect(this.db.pragma('table_info', { simple: true })).to.be.undefined; | ||
}); | ||
}); |
'use strict'; | ||
const { expect } = require('chai'); | ||
const Database = require('../.'); | ||
const util = require('./util'); | ||
describe('Database#prepare()', function () { | ||
const assertStmt = (stmt, source, db, returnsData) => { | ||
beforeEach(function () { | ||
this.db = new Database(util.next()); | ||
}); | ||
afterEach(function () { | ||
this.db.close(); | ||
}); | ||
const assertStmt = (stmt, source, db, reader) => { | ||
expect(stmt.source).to.equal(source); | ||
expect(stmt.constructor.name).to.equal('Statement'); | ||
expect(stmt.database).to.equal(db); | ||
expect(stmt.returnsData).to.equal(returnsData); | ||
expect(stmt.reader).to.equal(reader); | ||
expect(() => new stmt.constructor(source)).to.throw(TypeError); | ||
}; | ||
it('should throw an exception if a string is not provided', function () { | ||
const db = new Database(util.next()); | ||
expect(() => db.prepare(123)).to.throw(TypeError); | ||
expect(() => db.prepare(0)).to.throw(TypeError); | ||
expect(() => db.prepare(null)).to.throw(TypeError); | ||
expect(() => db.prepare()).to.throw(TypeError); | ||
expect(() => db.prepare(new String('CREATE TABLE people (name TEXT)'))).to.throw(TypeError); | ||
expect(() => this.db.prepare(123)).to.throw(TypeError); | ||
expect(() => this.db.prepare(0)).to.throw(TypeError); | ||
expect(() => this.db.prepare(null)).to.throw(TypeError); | ||
expect(() => this.db.prepare()).to.throw(TypeError); | ||
expect(() => this.db.prepare(new String('CREATE TABLE people (name TEXT)'))).to.throw(TypeError); | ||
}); | ||
it('should throw an exception if invalid SQL is provided', function () { | ||
const db = new Database(util.next()); | ||
expect(() => db.prepare('CREATE TABLE people (name TEXT')).to.throw(Database.SqliteError).with.property('code', 'SQLITE_ERROR'); | ||
expect(() => db.prepare('INSERT INTO people VALUES (?)')).to.throw(Database.SqliteError).with.property('code', 'SQLITE_ERROR'); | ||
expect(() => this.db.prepare('CREATE TABLE people (name TEXT')).to.throw(Database.SqliteError).with.property('code', 'SQLITE_ERROR'); | ||
expect(() => this.db.prepare('INSERT INTO people VALUES (?)')).to.throw(Database.SqliteError).with.property('code', 'SQLITE_ERROR'); | ||
}); | ||
it('should throw an exception if no statements are provided', function () { | ||
const db = new Database(util.next()); | ||
expect(() => db.prepare('')).to.throw(RangeError); | ||
expect(() => db.prepare(';')).to.throw(RangeError); | ||
expect(() => this.db.prepare('')).to.throw(RangeError); | ||
expect(() => this.db.prepare(';')).to.throw(RangeError); | ||
}); | ||
it('should throw an exception if more than one statement is provided', function () { | ||
const db = new Database(util.next()); | ||
expect(() => db.prepare('CREATE TABLE people (name TEXT);CREATE TABLE animals (name TEXT)')).to.throw(RangeError); | ||
expect(() => db.prepare('CREATE TABLE people (name TEXT); ')).to.throw(RangeError); | ||
expect(() => db.prepare('CREATE TABLE people (name TEXT);;')).to.throw(RangeError); | ||
expect(() => this.db.prepare('CREATE TABLE people (name TEXT);CREATE TABLE animals (name TEXT)')).to.throw(RangeError); | ||
expect(() => this.db.prepare('CREATE TABLE people (name TEXT);;')).to.throw(RangeError); | ||
}); | ||
it('should create a prepared Statement object', function () { | ||
const db = new Database(util.next()); | ||
const stmt1 = db.prepare('CREATE TABLE people (name TEXT)'); | ||
const stmt2 = db.prepare('CREATE TABLE people (name TEXT);'); | ||
assertStmt(stmt1, 'CREATE TABLE people (name TEXT)', db, false); | ||
assertStmt(stmt2, 'CREATE TABLE people (name TEXT);', db, false); | ||
const stmt1 = this.db.prepare('CREATE TABLE people (name TEXT) '); | ||
const stmt2 = this.db.prepare('CREATE TABLE people (name TEXT); '); | ||
assertStmt(stmt1, 'CREATE TABLE people (name TEXT) ', this.db, false); | ||
assertStmt(stmt2, 'CREATE TABLE people (name TEXT); ', this.db, false); | ||
expect(stmt1).to.not.equal(stmt2); | ||
expect(stmt1).to.not.equal(db.prepare('CREATE TABLE people (name TEXT)')); | ||
expect(stmt1).to.not.equal(this.db.prepare('CREATE TABLE people (name TEXT) ')); | ||
}); | ||
it('should create a prepared Statement object with just an expression', function () { | ||
const db = new Database(util.next()); | ||
const stmt = db.prepare('SELECT 555'); | ||
assertStmt(stmt, 'SELECT 555', db, true); | ||
const stmt = this.db.prepare('SELECT 555'); | ||
assertStmt(stmt, 'SELECT 555', this.db, true); | ||
}); | ||
}); |
'use strict'; | ||
const { expect } = require('chai'); | ||
const Database = require('../.'); | ||
const db = new Database(require('./util').next()); | ||
describe('Database#exec()', function () { | ||
beforeEach(function () { | ||
this.db = new Database(util.next()); | ||
}); | ||
afterEach(function () { | ||
this.db.close(); | ||
}); | ||
it('should throw an exception if a string is not provided', function () { | ||
expect(() => db.exec(123)).to.throw(TypeError); | ||
expect(() => db.exec(0)).to.throw(TypeError); | ||
expect(() => db.exec(null)).to.throw(TypeError); | ||
expect(() => db.exec()).to.throw(TypeError); | ||
expect(() => db.exec(new String('CREATE TABLE entries (a TEXT, b INTEGER)'))).to.throw(TypeError); | ||
expect(() => this.db.exec(123)).to.throw(TypeError); | ||
expect(() => this.db.exec(0)).to.throw(TypeError); | ||
expect(() => this.db.exec(null)).to.throw(TypeError); | ||
expect(() => this.db.exec()).to.throw(TypeError); | ||
expect(() => this.db.exec(new String('CREATE TABLE entries (a TEXT, b INTEGER)'))).to.throw(TypeError); | ||
}); | ||
it('should throw an exception if invalid SQL is provided', function () { | ||
expect(() => db.exec('CREATE TABLE entries (a TEXT, b INTEGER')).to.throw(Database.SqliteError).with.property('code', 'SQLITE_ERROR'); | ||
expect(() => this.db.exec('CREATE TABLE entries (a TEXT, b INTEGER')).to.throw(Database.SqliteError).with.property('code', 'SQLITE_ERROR'); | ||
}); | ||
it('should obey the restrictions of readonly mode', function () { | ||
const db2 = new Database(db.name, { readonly: true }); | ||
expect(() => db2.exec('CREATE TABLE people (name TEXT)')).to.throw(Database.SqliteError).with.property('code', 'SQLITE_READONLY'); | ||
db2.exec('SELECT 555'); | ||
this.db.close(); | ||
this.db = new Database(util.current(), { readonly: true }); | ||
expect(() => this.db.exec('CREATE TABLE people (name TEXT)')).to.throw(Database.SqliteError).with.property('code', 'SQLITE_READONLY'); | ||
this.db.exec('SELECT 555'); | ||
}); | ||
@@ -25,11 +31,11 @@ it('should execute the SQL, returning the database object itself', function () { | ||
const r1 = db.exec('CREATE TABLE entries (a TEXT, b INTEGER)'); | ||
const r2 = db.exec('INSERT INTO entries VALUES (\'foobar\', 44); INSERT INTO entries VALUES (\'baz\', NULL);'); | ||
const r3 = db.exec('SELECT * FROM entries'); | ||
const r1 = this.db.exec('CREATE TABLE entries (a TEXT, b INTEGER)'); | ||
const r2 = this.db.exec("INSERT INTO entries VALUES ('foobar', 44); INSERT INTO entries VALUES ('baz', NULL);"); | ||
const r3 = this.db.exec('SELECT * FROM entries'); | ||
expect(r1).to.equal(db); | ||
expect(r2).to.equal(db); | ||
expect(r3).to.equal(db); | ||
expect(r1).to.equal(this.db); | ||
expect(r2).to.equal(this.db); | ||
expect(r3).to.equal(this.db); | ||
const rows = db.prepare('SELECT * FROM entries ORDER BY rowid').all(); | ||
const rows = this.db.prepare('SELECT * FROM entries ORDER BY rowid').all(); | ||
expect(rows.length).to.equal(2); | ||
@@ -36,0 +42,0 @@ expect(rows[0].a).to.equal('foobar'); |
'use strict'; | ||
const { expect } = require('chai'); | ||
const Database = require('../.'); | ||
const db = new Database(require('./util').next()); | ||
describe('Statement#run()', function () { | ||
beforeEach(function () { | ||
this.db = new Database(util.next()); | ||
this.db.init = (data = false) => { | ||
this.db.info = this.db.prepare('CREATE TABLE entries (a TEXT, b INTEGER, c REAL, d BLOB)').run(); | ||
if (data) { | ||
this.db.prepare('CREATE TABLE people (id INTEGER PRIMARY KEY, name TEXT)').run(); | ||
this.db.prepare('CREATE TABLE ages (age INTEGER, person INTEGER NOT NULL REFERENCES people(id) ON DELETE CASCADE ON UPDATE CASCADE)').run(); | ||
this.db.prepare("INSERT INTO entries VALUES ('foo', 25, 3.14, x'1133ddff'), ('foo', 25, 3.14, x'1133ddff'), ('foo', 25, 3.14, x'1133ddff')").run(); | ||
this.db.prepare("INSERT INTO people VALUES (1, 'bob'), (2, 'sarah')").run(); | ||
} | ||
return this.db; | ||
}; | ||
}); | ||
afterEach(function () { | ||
this.db.close(); | ||
}); | ||
it('should throw an exception when used on a statement that returns data', function () { | ||
const stmt = db.prepare('SELECT 555'); | ||
const stmt = this.db.prepare('SELECT 555'); | ||
expect(() => stmt.run()).to.throw(TypeError); | ||
}); | ||
it('should work with CREATE TABLE', function () { | ||
const stmt = db.prepare('CREATE TABLE entries (a TEXT, b INTEGER, c REAL, d BLOB)'); | ||
const info = stmt.run(); | ||
const { info } = this.db.init(); | ||
expect(info.changes).to.equal(0); | ||
expect(info.lastInsertROWID).to.equal(0); | ||
expect(info.lastInsertRowid).to.equal(0); | ||
}); | ||
it('should work with CREATE TABLE IF NOT EXISTS', function () { | ||
const stmt = db.prepare('CREATE TABLE IF NOT EXISTS entries (a TEXT, b INTEGER, c REAL, d BLOB)'); | ||
const stmt = this.db.init().prepare('CREATE TABLE IF NOT EXISTS entries (a TEXT, b INTEGER, c REAL, d BLOB)'); | ||
const info = stmt.run(); | ||
expect(info.changes).to.equal(0); | ||
expect(info.lastInsertROWID).to.equal(0); | ||
expect(info.lastInsertRowid).to.equal(0); | ||
}); | ||
it('should work with INSERT INTO', function () { | ||
let stmt = db.prepare("INSERT INTO entries VALUES ('foo', 25, 3.14, x'1133ddff')"); | ||
let stmt = this.db.init().prepare("INSERT INTO entries VALUES ('foo', 25, 3.14, x'1133ddff')"); | ||
let info = stmt.run(); | ||
expect(info.changes).to.equal(1); | ||
expect(info.lastInsertROWID).to.equal(1); | ||
expect(info.lastInsertRowid).to.equal(1); | ||
info = stmt.run(); | ||
expect(info.changes).to.equal(1); | ||
expect(info.lastInsertROWID).to.equal(2); | ||
expect(info.lastInsertRowid).to.equal(2); | ||
stmt = db.prepare("INSERT INTO entries VALUES ('foo', 25, 3.14, x'1133ddff'), ('foo', 25, 3.14, x'1133ddff')"); | ||
stmt = this.db.prepare("INSERT INTO entries VALUES ('foo', 25, 3.14, x'1133ddff'), ('foo', 25, 3.14, x'1133ddff')"); | ||
info = stmt.run(); | ||
expect(info.changes).to.equal(2); | ||
expect(info.lastInsertROWID).to.equal(4); | ||
expect(info.lastInsertRowid).to.equal(4); | ||
}); | ||
it('should work with UPDATE', function () { | ||
const stmt = db.prepare("UPDATE entries SET a='bar' WHERE rowid=1"); | ||
const stmt = this.db.init(true).prepare("UPDATE entries SET a='bar' WHERE rowid=1"); | ||
expect(stmt.run().changes).to.equal(1); | ||
}); | ||
it('should work with DELETE FROM', function () { | ||
let stmt = db.prepare("DELETE FROM entries WHERE a='foo'"); | ||
let stmt = this.db.init(true).prepare("DELETE FROM entries WHERE a='foo'"); | ||
expect(stmt.run().changes).to.equal(3); | ||
stmt = db.prepare("INSERT INTO entries VALUES ('foo', 25, 3.14, x'1133ddff')"); | ||
stmt = this.db.prepare("INSERT INTO entries VALUES ('foo', 25, 3.14, x'1133ddff')"); | ||
stmt.run(); | ||
const info = stmt.run(); | ||
expect(info.changes).to.equal(1); | ||
expect(info.lastInsertROWID).to.equal(2); | ||
expect(info.lastInsertRowid).to.equal(2); | ||
}); | ||
it('should work with BEGIN and COMMIT', function () { | ||
expect(db.inTransaction).to.equal(false); | ||
expect(db.prepare("BEGIN TRANSACTION").run().changes).to.equal(0); | ||
expect(db.inTransaction).to.equal(true); | ||
const info = db.prepare("INSERT INTO entries VALUES ('foo', 25, 3.14, x'1133ddff')").run(); | ||
expect(this.db.init(true).inTransaction).to.equal(false); | ||
expect(this.db.prepare("BEGIN TRANSACTION").run().changes).to.equal(0); | ||
expect(this.db.inTransaction).to.equal(true); | ||
const info = this.db.prepare("INSERT INTO entries VALUES ('foo', 25, 3.14, x'1133ddff')").run(); | ||
expect(info.changes).to.equal(1); | ||
expect(info.lastInsertROWID).to.equal(3); | ||
expect(db.inTransaction).to.equal(true); | ||
expect(db.prepare("COMMIT TRANSACTION").run().changes).to.equal(0); | ||
expect(db.inTransaction).to.equal(false); | ||
expect(info.lastInsertRowid).to.equal(4); | ||
expect(this.db.inTransaction).to.equal(true); | ||
expect(this.db.prepare("COMMIT TRANSACTION").run().changes).to.equal(0); | ||
expect(this.db.inTransaction).to.equal(false); | ||
}); | ||
it('should work with DROP TABLE', function () { | ||
const stmt = db.prepare("DROP TABLE entries"); | ||
const stmt = this.db.init(true).prepare("DROP TABLE entries"); | ||
expect(stmt.run().changes).to.equal(0); | ||
}); | ||
it('should throw an exception for failed constraints', function () { | ||
db.prepare('CREATE TABLE people (id INTEGER PRIMARY KEY, name TEXT)').run(); | ||
db.prepare('CREATE TABLE ages (age INTEGER, person INTEGER NOT NULL REFERENCES people ON DELETE CASCADE ON UPDATE CASCADE)').run(); | ||
db.prepare("INSERT INTO people VALUES (NULL, 'bob')").run(); | ||
db.prepare("INSERT INTO people VALUES (NULL, 'sarah')").run(); | ||
db.prepare("INSERT INTO ages VALUES (25, 1)").run(); | ||
db.prepare("INSERT INTO ages VALUES (30, 2)").run(); | ||
db.prepare("INSERT INTO ages VALUES (35, 2)").run(); | ||
let stmt = db.prepare("INSERT INTO ages VALUES (30, 3)"); | ||
this.db.init(true).prepare("INSERT INTO ages VALUES (25, 1)").run(); | ||
this.db.prepare("INSERT INTO ages VALUES (30, 2)").run(); | ||
this.db.prepare("INSERT INTO ages VALUES (35, 2)").run(); | ||
let stmt = this.db.prepare("INSERT INTO ages VALUES (30, 3)"); | ||
expect(() => stmt.run()).to.throw(Database.SqliteError).with.property('code', 'SQLITE_CONSTRAINT_FOREIGNKEY'); | ||
stmt = db.prepare("INSERT INTO ages VALUES (30, NULL)"); | ||
stmt = this.db.prepare("INSERT INTO ages VALUES (30, NULL)"); | ||
expect(() => stmt.run()).to.throw(Database.SqliteError).with.property('code', 'SQLITE_CONSTRAINT_NOTNULL'); | ||
}); | ||
it('should allow ad-hoc transactions', function () { | ||
expect(db.prepare("BEGIN TRANSACTION").run().changes).to.equal(0); | ||
expect(db.prepare("INSERT INTO ages VALUES (45, 2)").run().changes).to.equal(1); | ||
const stmt = db.prepare("INSERT INTO ages VALUES (30, 3)"); | ||
expect(this.db.init(true).prepare("BEGIN TRANSACTION").run().changes).to.equal(0); | ||
expect(this.db.prepare("INSERT INTO ages VALUES (45, 2)").run().changes).to.equal(1); | ||
const stmt = this.db.prepare("INSERT INTO ages VALUES (30, 3)"); | ||
expect(() => stmt.run()).to.throw(Database.SqliteError).with.property('code', 'SQLITE_CONSTRAINT_FOREIGNKEY'); | ||
expect(db.prepare("ROLLBACK TRANSACTION").run().changes).to.equal(0); | ||
expect(this.db.prepare("ROLLBACK TRANSACTION").run().changes).to.equal(0); | ||
}); | ||
it('should not count changes from indirect mechanisms', function () { | ||
const stmt = db.prepare("UPDATE people SET id=55 WHERE id=2"); | ||
const stmt = this.db.init(true).prepare("UPDATE people SET id=55 WHERE id=2"); | ||
expect(stmt.run().changes).to.equal(1); | ||
}); | ||
it('should count accurate DELETE changes when a dropped table has side effects', function () { | ||
const stmt = db.prepare("DROP TABLE people"); | ||
const stmt = this.db.init(true).prepare("DROP TABLE people"); | ||
expect(stmt.run().changes).to.equal(2); | ||
}); | ||
it('should obey the restrictions of readonly mode', function () { | ||
const db2 = new Database(db.name, { readonly: true }); | ||
const stmt = db2.prepare('CREATE TABLE people (name TEXT)'); | ||
this.db.close(); | ||
this.db = new Database(util.current(), { readonly: true }); | ||
const stmt = this.db.prepare('CREATE TABLE people (name TEXT)'); | ||
expect(() => stmt.run()).to.throw(Database.SqliteError).with.property('code', 'SQLITE_READONLY'); | ||
}); | ||
it('should accept bind parameters', function () { | ||
db.prepare("CREATE TABLE entries (a TEXT CHECK(typeof(a)=='text'), b INTEGER CHECK(typeof(b)=='integer' OR typeof(b)=='real'), c REAL CHECK(typeof(c)=='real' OR typeof(c)=='integer'), d BLOB CHECK(typeof(d)=='blob'))").run(); | ||
db.prepare('INSERT INTO entries VALUES (?, ?, ?, ?)').run('foo', 25, 25, Buffer.alloc(8).fill(0xdd)); | ||
db.prepare('INSERT INTO entries VALUES (?, ?, ?, ?)').run(['foo', 25, 25, Buffer.alloc(8).fill(0xdd)]); | ||
db.prepare('INSERT INTO entries VALUES (?, ?, ?, ?)').run(['foo', 25], [25], Buffer.alloc(8).fill(0xdd)); | ||
db.prepare('INSERT INTO entries VALUES (@a, @b, @c, @d)').run({ a: 'foo', b: 25, c: 25, d: Buffer.alloc(8).fill(0xdd) }); | ||
db.prepare('INSERT INTO entries VALUES ($a, $b, $c, $d)').run({ a: 'foo', b: 25, c: 25, d: Buffer.alloc(8).fill(0xdd) }); | ||
db.prepare('INSERT INTO entries VALUES (:a, :b, :c, :d)').run({ a: 'foo', b: 25, c: 25, d: Buffer.alloc(8).fill(0xdd) }); | ||
db.prepare('INSERT INTO entries VALUES (?, @a, @a, ?)').run({ a: 25 }, ['foo'], Buffer.alloc(8).fill(0xdd)); | ||
this.db.prepare("CREATE TABLE entries (a TEXT CHECK(typeof(a)=='text'), b INTEGER CHECK(typeof(b)=='integer' OR typeof(b)=='real'), c REAL CHECK(typeof(c)=='real' OR typeof(c)=='integer'), d BLOB CHECK(typeof(d)=='blob'))").run(); | ||
this.db.prepare('INSERT INTO entries VALUES (?, ?, ?, ?)').run('foo', 25, 25, Buffer.alloc(8).fill(0xdd)); | ||
this.db.prepare('INSERT INTO entries VALUES (?, ?, ?, ?)').run(['foo', 25, 25, Buffer.alloc(8).fill(0xdd)]); | ||
this.db.prepare('INSERT INTO entries VALUES (?, ?, ?, ?)').run(['foo', 25], [25], Buffer.alloc(8).fill(0xdd)); | ||
this.db.prepare('INSERT INTO entries VALUES (@a, @b, @c, @d)').run({ a: 'foo', b: 25, c: 25, d: Buffer.alloc(8).fill(0xdd) }); | ||
this.db.prepare('INSERT INTO entries VALUES ($a, $b, $c, $d)').run({ a: 'foo', b: 25, c: 25, d: Buffer.alloc(8).fill(0xdd) }); | ||
this.db.prepare('INSERT INTO entries VALUES (:a, :b, :c, :d)').run({ a: 'foo', b: 25, c: 25, d: Buffer.alloc(8).fill(0xdd) }); | ||
this.db.prepare('INSERT INTO entries VALUES (?, @a, @a, ?)').run({ a: 25 }, ['foo'], Buffer.alloc(8).fill(0xdd)); | ||
expect(() => | ||
db.prepare('INSERT INTO entries VALUES (?, @a, @a, ?)').run({ a: 25 }, ['foo'], Buffer.alloc(8).fill(0xdd), Buffer.alloc(8).fill(0xdd)) | ||
this.db.prepare('INSERT INTO entries VALUES (?, @a, @a, ?)').run({ a: 25 }, ['foo'], Buffer.alloc(8).fill(0xdd), Buffer.alloc(8).fill(0xdd)) | ||
).to.throw(RangeError); | ||
expect(() => | ||
db.prepare('INSERT INTO entries VALUES (?, @a, @a, ?)').run({ a: 25 }, ['foo']) | ||
this.db.prepare('INSERT INTO entries VALUES (?, @a, @a, ?)').run({ a: 25 }, ['foo']) | ||
).to.throw(RangeError); | ||
db.prepare('INSERT INTO entries VALUES (?, @a, @a, ?)').run({ a: 25, c: 25 }, ['foo'], Buffer.alloc(8).fill(0xdd)); | ||
this.db.prepare('INSERT INTO entries VALUES (?, @a, @a, ?)').run({ a: 25, c: 25 }, ['foo'], Buffer.alloc(8).fill(0xdd)); | ||
expect(() => | ||
db.prepare('INSERT INTO entries VALUES (?, @a, @a, ?)').run({}, ['foo'], Buffer.alloc(8).fill(0xdd)) | ||
this.db.prepare('INSERT INTO entries VALUES (?, @a, @a, ?)').run({}, ['foo'], Buffer.alloc(8).fill(0xdd)) | ||
).to.throw(RangeError); | ||
expect(() => | ||
db.prepare('INSERT INTO entries VALUES (?, ?, ?, ?)').run(25, 'foo', 25, Buffer.alloc(8).fill(0xdd)) | ||
this.db.prepare('INSERT INTO entries VALUES (?, ?, ?, ?)').run(25, 'foo', 25, Buffer.alloc(8).fill(0xdd)) | ||
).to.throw(Database.SqliteError).with.property('code', 'SQLITE_CONSTRAINT_CHECK'); | ||
db.prepare('INSERT INTO entries VALUES (?, ?, ?, ?)').run('foo', 25, 25, Buffer.alloc(8).fill(0xdd), {}); | ||
db.prepare('INSERT INTO entries VALUES (?, ?, ?, ?)').run('foo', 25, 25, Buffer.alloc(8).fill(0xdd), { foo: 'foo' }); | ||
this.db.prepare('INSERT INTO entries VALUES (?, ?, ?, ?)').run('foo', 25, 25, Buffer.alloc(8).fill(0xdd), {}); | ||
this.db.prepare('INSERT INTO entries VALUES (?, ?, ?, ?)').run('foo', 25, 25, Buffer.alloc(8).fill(0xdd), { foo: 'foo' }); | ||
expect(() => | ||
db.prepare('INSERT INTO entries VALUES (?, ?, ?, ?)').run('foo', 25, 25, { 4: Buffer.alloc(8).fill(0xdd) }) | ||
this.db.prepare('INSERT INTO entries VALUES (?, ?, ?, ?)').run('foo', 25, 25, { 4: Buffer.alloc(8).fill(0xdd) }) | ||
).to.throw(RangeError); | ||
expect(() => | ||
db.prepare('INSERT INTO entries VALUES (?, ?, ?, ?)').run() | ||
this.db.prepare('INSERT INTO entries VALUES (?, ?, ?, ?)').run() | ||
).to.throw(RangeError); | ||
expect(() => | ||
db.prepare('INSERT INTO entries VALUES (?, ?, ?, ?)').run({ length: 4, 0: 'foo', 1: 25, 2: 25, 3: Buffer.alloc(8).fill(0xdd) }) | ||
this.db.prepare('INSERT INTO entries VALUES (?, ?, ?, ?)').run({ length: 4, 0: 'foo', 1: 25, 2: 25, 3: Buffer.alloc(8).fill(0xdd) }) | ||
).to.throw(RangeError); | ||
expect(() => | ||
db.prepare('INSERT INTO entries VALUES (?, ?, ?, ?)').run('foo', 25, new Number(25), Buffer.alloc(8).fill(0xdd)) | ||
this.db.prepare('INSERT INTO entries VALUES (?, ?, ?, ?)').run('foo', 25, new Number(25), Buffer.alloc(8).fill(0xdd)) | ||
).to.throw(TypeError); | ||
expect(() => | ||
db.prepare('INSERT INTO entries VALUES (?, ?, ?, ?)').run('foo', { low: 25, high: 25 }, 25, Buffer.alloc(8).fill(0xdd)) | ||
this.db.prepare('INSERT INTO entries VALUES (?, ?, ?, ?)').run('foo', { low: 25, high: 25 }, 25, Buffer.alloc(8).fill(0xdd)) | ||
).to.throw(RangeError); | ||
@@ -145,3 +157,3 @@ function Foo() { | ||
expect(() => | ||
db.prepare('INSERT INTO entries VALUES (@a, @b, @c, @d)').run(new Foo) | ||
this.db.prepare('INSERT INTO entries VALUES (@a, @b, @c, @d)').run(new Foo) | ||
).to.throw(TypeError); | ||
@@ -152,3 +164,3 @@ | ||
let row; | ||
while (row = db.prepare(`SELECT * FROM entries WHERE rowid=${++i}`).get()) { | ||
while (row = this.db.prepare(`SELECT * FROM entries WHERE rowid=${++i}`).get()) { | ||
expect(row).to.deep.equal({ a: 'foo', b: 25, c: 25, d: Buffer.alloc(8).fill(0xdd) }); | ||
@@ -155,0 +167,0 @@ } |
'use strict'; | ||
const { expect } = require('chai'); | ||
const Database = require('../.'); | ||
const db = new Database(require('./util').next()); | ||
describe('Statement#get()', function () { | ||
beforeEach(function () { | ||
this.db = new Database(util.next()); | ||
this.db.prepare('CREATE TABLE entries (a TEXT, b INTEGER, c REAL, d BLOB, e TEXT)').run(); | ||
this.db.prepare("INSERT INTO entries WITH RECURSIVE temp(a, b, c, d, e) AS (SELECT 'foo', 1, 3.14, x'dddddddd', NULL UNION ALL SELECT a, b + 1, c, d, e FROM temp LIMIT 10) SELECT * FROM temp").run(); | ||
}); | ||
afterEach(function () { | ||
this.db.close(); | ||
}); | ||
it('should throw an exception when used on a statement that returns no data', function () { | ||
db.prepare('CREATE TABLE entries (a TEXT, b INTEGER, c REAL, d BLOB, e TEXT)').run(); | ||
let stmt = db.prepare("INSERT INTO entries VALUES ('foo', 1, 3.14, x'dddddddd', NULL)"); | ||
expect(stmt.returnsData).to.be.false; | ||
let stmt = this.db.prepare("INSERT INTO entries VALUES ('foo', 1, 3.14, x'dddddddd', NULL)"); | ||
expect(stmt.reader).to.be.false; | ||
expect(() => stmt.get()).to.throw(TypeError); | ||
stmt = db.prepare("CREATE TABLE IF NOT EXISTS entries (a TEXT, b INTEGER, c REAL, d BLOB, e TEXT)"); | ||
expect(stmt.returnsData).to.be.false; | ||
stmt = this.db.prepare("CREATE TABLE IF NOT EXISTS entries (a TEXT, b INTEGER, c REAL, d BLOB, e TEXT)"); | ||
expect(stmt.reader).to.be.false; | ||
expect(() => stmt.get()).to.throw(TypeError); | ||
stmt = db.prepare("BEGIN TRANSACTION"); | ||
expect(stmt.returnsData).to.be.false; | ||
stmt = this.db.prepare("BEGIN TRANSACTION"); | ||
expect(stmt.reader).to.be.false; | ||
expect(() => stmt.get()).to.throw(TypeError); | ||
}); | ||
it('should return the first matching row', function () { | ||
db.prepare("INSERT INTO entries WITH RECURSIVE temp(a, b, c, d, e) AS (SELECT 'foo', 1, 3.14, x'dddddddd', NULL UNION ALL SELECT a, b + 1, c, d, e FROM temp LIMIT 10) SELECT * FROM temp").run(); | ||
let stmt = db.prepare("SELECT * FROM entries ORDER BY rowid"); | ||
expect(stmt.returnsData).to.be.true; | ||
let stmt = this.db.prepare("SELECT * FROM entries ORDER BY rowid"); | ||
expect(stmt.reader).to.be.true; | ||
expect(stmt.get()).to.deep.equal({ a: 'foo', b: 1, c: 3.14, d: Buffer.alloc(4).fill(0xdd), e: null }); | ||
stmt = db.prepare("SELECT * FROM entries WHERE b > 5 ORDER BY rowid"); | ||
stmt = this.db.prepare("SELECT * FROM entries WHERE b > 5 ORDER BY rowid"); | ||
expect(stmt.get()).to.deep.equal({ a: 'foo', b: 6, c: 3.14, d: Buffer.alloc(4).fill(0xdd), e: null }); | ||
}); | ||
it('should obey the current pluck setting', function () { | ||
const stmt = db.prepare("SELECT * FROM entries ORDER BY rowid"); | ||
const row = { a: 'foo', b: 1, c: 3.14, d: Buffer.alloc(4).fill(0xdd), e: null }; | ||
it('should obey the current pluck and expand settings', function () { | ||
const stmt = this.db.prepare("SELECT *, 2 + 3.5 AS c FROM entries ORDER BY rowid"); | ||
const expanded = { entries: { a: 'foo', b: 1, c: 3.14, d: Buffer.alloc(4).fill(0xdd), e: null }, $: { c: 5.5 } }; | ||
const row = { ...expanded.entries, ...expanded.$ }; | ||
const plucked = expanded.entries.a; | ||
expect(stmt.get()).to.deep.equal(row); | ||
expect(stmt.pluck(true).get()).to.equal('foo'); | ||
expect(stmt.get()).to.equal('foo'); | ||
expect(stmt.pluck(true).get()).to.equal(plucked); | ||
expect(stmt.get()).to.equal(plucked); | ||
expect(stmt.pluck(false).get()).to.deep.equal(row); | ||
expect(stmt.get()).to.deep.equal(row); | ||
expect(stmt.pluck().get()).to.equal('foo'); | ||
expect(stmt.get()).to.equal('foo'); | ||
expect(stmt.pluck().get()).to.equal(plucked); | ||
expect(stmt.get()).to.equal(plucked); | ||
expect(stmt.expand().get()).to.deep.equal(expanded); | ||
expect(stmt.get()).to.deep.equal(expanded); | ||
expect(stmt.expand(false).get()).to.deep.equal(row); | ||
expect(stmt.get()).to.deep.equal(row); | ||
expect(stmt.expand(true).get()).to.deep.equal(expanded); | ||
expect(stmt.get()).to.deep.equal(expanded); | ||
expect(stmt.pluck(true).get()).to.equal(plucked); | ||
expect(stmt.get()).to.equal(plucked); | ||
}); | ||
it('should return undefined when no rows were found', function () { | ||
const stmt = db.prepare("SELECT * FROM entries WHERE b == 999"); | ||
const stmt = this.db.prepare("SELECT * FROM entries WHERE b == 999"); | ||
expect(stmt.get()).to.be.undefined; | ||
@@ -52,29 +65,29 @@ expect(stmt.pluck().get()).to.be.undefined; | ||
const SQL2 = 'SELECT * FROM entries WHERE a=@a AND b=@b AND c=@c AND d=@d AND e IS @e'; | ||
let result = db.prepare(SQL1).get('foo', 1, 3.14, Buffer.alloc(4).fill(0xdd), null); | ||
let result = this.db.prepare(SQL1).get('foo', 1, 3.14, Buffer.alloc(4).fill(0xdd), null); | ||
expect(result).to.deep.equal(row); | ||
result = db.prepare(SQL1).get(['foo', 1, 3.14, Buffer.alloc(4).fill(0xdd), null]); | ||
result = this.db.prepare(SQL1).get(['foo', 1, 3.14, Buffer.alloc(4).fill(0xdd), null]); | ||
expect(result).to.deep.equal(row); | ||
result = db.prepare(SQL1).get(['foo', 1], [3.14], Buffer.alloc(4).fill(0xdd), [,]); | ||
result = this.db.prepare(SQL1).get(['foo', 1], [3.14], Buffer.alloc(4).fill(0xdd), [,]); | ||
expect(result).to.deep.equal(row); | ||
result = db.prepare(SQL2).get({ a: 'foo', b: 1, c: 3.14, d: Buffer.alloc(4).fill(0xdd), e: undefined }); | ||
result = this.db.prepare(SQL2).get({ a: 'foo', b: 1, c: 3.14, d: Buffer.alloc(4).fill(0xdd), e: undefined }); | ||
expect(result).to.deep.equal(row); | ||
result = db.prepare(SQL2).get({ a: 'foo', b: 1, c: 3.14, d: Buffer.alloc(4).fill(0xaa), e: undefined }); | ||
result = this.db.prepare(SQL2).get({ a: 'foo', b: 1, c: 3.14, d: Buffer.alloc(4).fill(0xaa), e: undefined }); | ||
expect(result).to.be.undefined; | ||
expect(() => | ||
db.prepare(SQL2).get({ a: 'foo', b: 1, c: 3.14, d: Buffer.alloc(4).fill(0xdd) }) | ||
this.db.prepare(SQL2).get({ a: 'foo', b: 1, c: 3.14, d: Buffer.alloc(4).fill(0xdd) }) | ||
).to.throw(RangeError); | ||
expect(() => | ||
db.prepare(SQL1).get() | ||
this.db.prepare(SQL1).get() | ||
).to.throw(RangeError); | ||
expect(() => | ||
db.prepare(SQL2).get({}) | ||
this.db.prepare(SQL2).get({}) | ||
).to.throw(RangeError); | ||
}); | ||
}); |
'use strict'; | ||
const { expect } = require('chai'); | ||
const Database = require('../.'); | ||
const db = new Database(require('./util').next()); | ||
describe('Statement#all()', function () { | ||
beforeEach(function () { | ||
this.db = new Database(util.next()); | ||
this.db.prepare('CREATE TABLE entries (a TEXT, b INTEGER, c REAL, d BLOB, e TEXT)').run(); | ||
this.db.prepare("INSERT INTO entries WITH RECURSIVE temp(a, b, c, d, e) AS (SELECT 'foo', 1, 3.14, x'dddddddd', NULL UNION ALL SELECT a, b + 1, c, d, e FROM temp LIMIT 10) SELECT * FROM temp").run(); | ||
}); | ||
afterEach(function () { | ||
this.db.close(); | ||
}); | ||
it('should throw an exception when used on a statement that returns no data', function () { | ||
db.prepare('CREATE TABLE entries (a TEXT, b INTEGER, c REAL, d BLOB, e TEXT)').run(); | ||
let stmt = db.prepare("INSERT INTO entries VALUES ('foo', 1, 3.14, x'dddddddd', NULL)"); | ||
expect(stmt.returnsData).to.be.false; | ||
let stmt = this.db.prepare("INSERT INTO entries VALUES ('foo', 1, 3.14, x'dddddddd', NULL)"); | ||
expect(stmt.reader).to.be.false; | ||
expect(() => stmt.all()).to.throw(TypeError); | ||
stmt = db.prepare("CREATE TABLE IF NOT EXISTS entries (a TEXT, b INTEGER, c REAL, d BLOB, e TEXT)"); | ||
expect(stmt.returnsData).to.be.false; | ||
stmt = this.db.prepare("CREATE TABLE IF NOT EXISTS entries (a TEXT, b INTEGER, c REAL, d BLOB, e TEXT)"); | ||
expect(stmt.reader).to.be.false; | ||
expect(() => stmt.all()).to.throw(TypeError); | ||
stmt = db.prepare("BEGIN TRANSACTION"); | ||
expect(stmt.returnsData).to.be.false; | ||
stmt = this.db.prepare("BEGIN TRANSACTION"); | ||
expect(stmt.reader).to.be.false; | ||
expect(() => stmt.all()).to.throw(TypeError); | ||
}); | ||
it('should return an array of every matching row', function () { | ||
db.prepare("INSERT INTO entries WITH RECURSIVE temp(a, b, c, d, e) AS (SELECT 'foo', 1, 3.14, x'dddddddd', NULL UNION ALL SELECT a, b + 1, c, d, e FROM temp LIMIT 10) SELECT * FROM temp").run(); | ||
const row = { a: 'foo', b: 1, c: 3.14, d: Buffer.alloc(4).fill(0xdd), e: null }; | ||
let stmt = db.prepare("SELECT * FROM entries ORDER BY rowid"); | ||
expect(stmt.returnsData).to.be.true; | ||
let stmt = this.db.prepare("SELECT * FROM entries ORDER BY rowid"); | ||
expect(stmt.reader).to.be.true; | ||
matchesFrom(stmt.all(), 1); | ||
stmt = db.prepare("SELECT * FROM entries WHERE b > 5 ORDER BY rowid"); | ||
stmt = this.db.prepare("SELECT * FROM entries WHERE b > 5 ORDER BY rowid"); | ||
matchesFrom(stmt.all(), 6); | ||
@@ -42,15 +46,28 @@ | ||
}); | ||
it('should obey the current pluck setting', function () { | ||
const stmt = db.prepare("SELECT * FROM entries"); | ||
const plucked = new Array(10).fill('foo'); | ||
expect(stmt.all()).to.not.deep.equal(plucked); | ||
it('should obey the current pluck and expand settings', function () { | ||
const stmt = this.db.prepare("SELECT *, 2 + 3.5 AS c FROM entries ORDER BY rowid"); | ||
const expanded = new Array(10).fill().map((_, i) => ({ | ||
entries: { a: 'foo', b: i + 1, c: 3.14, d: Buffer.alloc(4).fill(0xdd), e: null }, | ||
$: { c: 5.5 }, | ||
})); | ||
const rows = expanded.map(x => ({ ...x.entries, ...x.$ })); | ||
const plucked = expanded.map(x => x.entries.a); | ||
expect(stmt.all()).to.deep.equal(rows); | ||
expect(stmt.pluck(true).all()).to.deep.equal(plucked); | ||
expect(stmt.all()).to.deep.equal(plucked); | ||
expect(stmt.pluck(false).all()).to.not.deep.equal(plucked); | ||
expect(stmt.all()).to.not.deep.equal(plucked); | ||
expect(stmt.pluck(false).all()).to.deep.equal(rows); | ||
expect(stmt.all()).to.deep.equal(rows); | ||
expect(stmt.pluck().all()).to.deep.equal(plucked); | ||
expect(stmt.all()).to.deep.equal(plucked); | ||
expect(stmt.expand().all()).to.deep.equal(expanded); | ||
expect(stmt.all()).to.deep.equal(expanded); | ||
expect(stmt.expand(false).all()).to.deep.equal(rows); | ||
expect(stmt.all()).to.deep.equal(rows); | ||
expect(stmt.expand(true).all()).to.deep.equal(expanded); | ||
expect(stmt.all()).to.deep.equal(expanded); | ||
expect(stmt.pluck(true).all()).to.deep.equal(plucked); | ||
expect(stmt.all()).to.deep.equal(plucked); | ||
}); | ||
it('should return an empty array when no rows were found', function () { | ||
const stmt = db.prepare("SELECT * FROM entries WHERE b == 999"); | ||
const stmt = this.db.prepare("SELECT * FROM entries WHERE b == 999"); | ||
expect(stmt.all()).to.deep.equal([]); | ||
@@ -63,29 +80,29 @@ expect(stmt.pluck().all()).to.deep.equal([]); | ||
const SQL2 = 'SELECT * FROM entries WHERE a=@a AND b=@b AND c=@c AND d=@d AND e IS @e'; | ||
let result = db.prepare(SQL1).all('foo', 1, 3.14, Buffer.alloc(4).fill(0xdd), null); | ||
let result = this.db.prepare(SQL1).all('foo', 1, 3.14, Buffer.alloc(4).fill(0xdd), null); | ||
expect(result).to.deep.equal(rows); | ||
result = db.prepare(SQL1).all(['foo', 1, 3.14, Buffer.alloc(4).fill(0xdd), null]); | ||
result = this.db.prepare(SQL1).all(['foo', 1, 3.14, Buffer.alloc(4).fill(0xdd), null]); | ||
expect(result).to.deep.equal(rows); | ||
result = db.prepare(SQL1).all(['foo', 1], [3.14], Buffer.alloc(4).fill(0xdd), [,]); | ||
result = this.db.prepare(SQL1).all(['foo', 1], [3.14], Buffer.alloc(4).fill(0xdd), [,]); | ||
expect(result).to.deep.equal(rows); | ||
result = db.prepare(SQL2).all({ a: 'foo', b: 1, c: 3.14, d: Buffer.alloc(4).fill(0xdd), e: undefined }); | ||
result = this.db.prepare(SQL2).all({ a: 'foo', b: 1, c: 3.14, d: Buffer.alloc(4).fill(0xdd), e: undefined }); | ||
expect(result).to.deep.equal(rows); | ||
result = db.prepare(SQL2).all({ a: 'foo', b: 1, c: 3.14, d: Buffer.alloc(4).fill(0xaa), e: undefined }); | ||
result = this.db.prepare(SQL2).all({ a: 'foo', b: 1, c: 3.14, d: Buffer.alloc(4).fill(0xaa), e: undefined }); | ||
expect(result).to.deep.equal([]); | ||
expect(() => | ||
db.prepare(SQL2).all({ a: 'foo', b: 1, c: 3.14, d: Buffer.alloc(4).fill(0xdd) }) | ||
this.db.prepare(SQL2).all({ a: 'foo', b: 1, c: 3.14, d: Buffer.alloc(4).fill(0xdd) }) | ||
).to.throw(RangeError); | ||
expect(() => | ||
db.prepare(SQL1).all() | ||
this.db.prepare(SQL1).all() | ||
).to.throw(RangeError); | ||
expect(() => | ||
db.prepare(SQL2).all({}) | ||
this.db.prepare(SQL2).all({}) | ||
).to.throw(RangeError); | ||
}); | ||
}); |
'use strict'; | ||
const { expect } = require('chai'); | ||
const Database = require('../.'); | ||
const db = new Database(require('./util').next()); | ||
describe('Statement#iterate()', function () { | ||
beforeEach(function () { | ||
this.db = new Database(util.next()); | ||
this.db.prepare('CREATE TABLE entries (a TEXT, b INTEGER, c REAL, d BLOB, e TEXT)').run(); | ||
this.db.prepare("INSERT INTO entries WITH RECURSIVE temp(a, b, c, d, e) AS (SELECT 'foo', 1, 3.14, x'dddddddd', NULL UNION ALL SELECT a, b + 1, c, d, e FROM temp LIMIT 10) SELECT * FROM temp").run(); | ||
}); | ||
afterEach(function () { | ||
this.db.close(); | ||
}); | ||
it('should throw an exception when used on a statement that returns no data', function () { | ||
db.prepare('CREATE TABLE entries (a TEXT, b INTEGER, c REAL, d BLOB, e TEXT)').run(); | ||
let stmt = db.prepare("INSERT INTO entries VALUES ('foo', 1, 3.14, x'dddddddd', NULL)"); | ||
expect(stmt.returnsData).to.be.false; | ||
let stmt = this.db.prepare("INSERT INTO entries VALUES ('foo', 1, 3.14, x'dddddddd', NULL)"); | ||
expect(stmt.reader).to.be.false; | ||
expect(() => stmt.iterate()).to.throw(TypeError); | ||
stmt = db.prepare("CREATE TABLE IF NOT EXISTS entries (a TEXT, b INTEGER, c REAL, d BLOB, e TEXT)"); | ||
expect(stmt.returnsData).to.be.false; | ||
stmt = this.db.prepare("CREATE TABLE IF NOT EXISTS entries (a TEXT, b INTEGER, c REAL, d BLOB, e TEXT)"); | ||
expect(stmt.reader).to.be.false; | ||
expect(() => stmt.iterate()).to.throw(TypeError); | ||
stmt = db.prepare("BEGIN TRANSACTION"); | ||
expect(stmt.returnsData).to.be.false; | ||
stmt = this.db.prepare("BEGIN TRANSACTION"); | ||
expect(stmt.reader).to.be.false; | ||
expect(() => stmt.iterate()).to.throw(TypeError); | ||
db.prepare("INSERT INTO entries WITH RECURSIVE temp(a, b, c, d, e) AS (SELECT 'foo', 1, 3.14, x'dddddddd', NULL UNION ALL SELECT a, b + 1, c, d, e FROM temp LIMIT 10) SELECT * FROM temp").run(); | ||
this.db.prepare("INSERT INTO entries WITH RECURSIVE temp(a, b, c, d, e) AS (SELECT 'foo', 1, 3.14, x'dddddddd', NULL UNION ALL SELECT a, b + 1, c, d, e FROM temp LIMIT 10) SELECT * FROM temp").run(); | ||
}); | ||
@@ -28,4 +33,4 @@ it('should return an iterator over each matching row', function () { | ||
let count = 0; | ||
let stmt = db.prepare("SELECT * FROM entries ORDER BY rowid"); | ||
expect(stmt.returnsData).to.be.true; | ||
let stmt = this.db.prepare("SELECT * FROM entries ORDER BY rowid"); | ||
expect(stmt.reader).to.be.true; | ||
@@ -48,3 +53,3 @@ const iterator = stmt.iterate(); | ||
count = 0; | ||
stmt = db.prepare("SELECT * FROM entries WHERE b > 5 ORDER BY rowid"); | ||
stmt = this.db.prepare("SELECT * FROM entries WHERE b > 5 ORDER BY rowid"); | ||
const iterator2 = stmt.iterate(); | ||
@@ -58,22 +63,13 @@ expect(iterator).to.not.equal(iterator2); | ||
}); | ||
it('should obey the current pluck setting', function () { | ||
const row = { a: 'foo', b: 1, c: 3.14, d: Buffer.alloc(4).fill(0xdd), e: null }; | ||
const stmt = db.prepare("SELECT * FROM entries ORDER BY rowid"); | ||
shouldHave(row); | ||
stmt.pluck(true); | ||
shouldHave('foo'); | ||
shouldHave('foo'); | ||
stmt.pluck(false); | ||
shouldHave(row); | ||
shouldHave(row); | ||
stmt.pluck(); | ||
shouldHave('foo'); | ||
shouldHave('foo'); | ||
function shouldHave(desiredData) { | ||
it('should obey the current pluck and expand settings', function () { | ||
const shouldHave = (desiredData) => { | ||
let i = 0; | ||
for (const data of stmt.iterate()) { | ||
++i; | ||
i += 1; | ||
if (typeof desiredData === 'object' && desiredData !== null) { | ||
desiredData.b = i; | ||
if (typeof desiredData.entries === 'object' && desiredData.entries !== null) { | ||
desiredData.entries.b = i; | ||
} else { | ||
desiredData.b = i; | ||
} | ||
} | ||
@@ -83,7 +79,36 @@ expect(data).to.deep.equal(desiredData); | ||
expect(i).to.equal(10); | ||
} | ||
}; | ||
const expanded = { | ||
entries: { a: 'foo', b: 1, c: 3.14, d: Buffer.alloc(4).fill(0xdd), e: null }, | ||
$: { c: 5.5 }, | ||
}; | ||
const row = { ...expanded.entries, ...expanded.$ }; | ||
const plucked = expanded.entries.a; | ||
const stmt = this.db.prepare("SELECT *, 2 + 3.5 AS c FROM entries ORDER BY rowid"); | ||
shouldHave(row); | ||
stmt.pluck(true); | ||
shouldHave(plucked); | ||
shouldHave(plucked); | ||
stmt.pluck(false); | ||
shouldHave(row); | ||
shouldHave(row); | ||
stmt.pluck(); | ||
shouldHave(plucked); | ||
shouldHave(plucked); | ||
stmt.expand(); | ||
shouldHave(expanded); | ||
shouldHave(expanded); | ||
stmt.expand(false); | ||
shouldHave(row); | ||
shouldHave(row); | ||
stmt.expand(true); | ||
shouldHave(expanded); | ||
shouldHave(expanded); | ||
stmt.pluck(true); | ||
shouldHave(plucked); | ||
shouldHave(plucked); | ||
}); | ||
it('should not be able to invoke .pluck() while the database is busy', function () { | ||
const stmt1 = db.prepare("SELECT * FROM entries"); | ||
const stmt2 = db.prepare("SELECT * FROM entries LIMIT 2"); | ||
const stmt1 = this.db.prepare("SELECT * FROM entries ORDER BY rowid"); | ||
const stmt2 = this.db.prepare("SELECT * FROM entries ORDER BY rowid LIMIT 2"); | ||
let i = 0; | ||
@@ -101,3 +126,3 @@ for (const data of stmt1.iterate()) { | ||
const err = new Error('foobar'); | ||
const stmt = db.prepare("SELECT * FROM entries"); | ||
const stmt = this.db.prepare("SELECT * FROM entries ORDER BY rowid"); | ||
const iterator = stmt.iterate(); | ||
@@ -116,3 +141,3 @@ let count = 0; | ||
it('should close the iterator when using break in a for-of loop', function () { | ||
const stmt = db.prepare("SELECT * FROM entries"); | ||
const stmt = this.db.prepare("SELECT * FROM entries ORDER BY rowid"); | ||
const iterator = stmt.iterate(); | ||
@@ -129,3 +154,3 @@ let count = 0; | ||
it('should return an empty iterator when no rows were found', function () { | ||
const stmt = db.prepare("SELECT * FROM entries WHERE b == 999"); | ||
const stmt = this.db.prepare("SELECT * FROM entries WHERE b == 999"); | ||
expect(stmt.iterate().next()).to.deep.equal({ value: undefined, done: true }); | ||
@@ -137,13 +162,12 @@ for (const data of stmt.pluck().iterate()) { | ||
it('should not allow other database operations to execute while open', function () { | ||
const stmt1 = db.prepare('SELECT * FROM entries'); | ||
const stmt2 = db.prepare('CREATE TABLE numbers (number INTEGER)'); | ||
const trans = db.transaction(['CREATE TABLE numbers (number INTEGER)']); | ||
const stmt1 = this.db.prepare('SELECT * FROM entries ORDER BY rowid'); | ||
const stmt2 = this.db.prepare('CREATE TABLE numbers (number INTEGER)'); | ||
let count = 0; | ||
for (const row of db.prepare('SELECT * FROM entries').iterate()) { | ||
for (const row of this.db.prepare('SELECT * FROM entries ORDER BY rowid').iterate()) { | ||
++count; | ||
expect(() => db.close()).to.throw(TypeError); | ||
expect(() => db.pragma('cache_size')).to.throw(TypeError); | ||
expect(() => db.checkpoint()).to.throw(TypeError); | ||
expect(() => db.prepare('SELECT * FROM entries')).to.throw(TypeError); | ||
expect(() => db.transaction(['CREATE TABLE numbers (number INTEGER)'])).to.throw(TypeError); | ||
expect(() => this.db.close()).to.throw(TypeError); | ||
expect(() => this.db.pragma('cache_size')).to.throw(TypeError); | ||
expect(() => this.db.checkpoint()).to.throw(TypeError); | ||
expect(() => this.db.prepare('SELECT * FROM entries ORDER BY rowid')).to.throw(TypeError); | ||
expect(() => this.db.transaction(() => {})).to.throw(TypeError); | ||
expect(() => stmt1.get()).to.throw(TypeError); | ||
@@ -153,3 +177,2 @@ expect(() => stmt1.all()).to.throw(TypeError); | ||
expect(() => stmt2.run()).to.throw(TypeError); | ||
expect(() => trans.run()).to.throw(TypeError); | ||
} | ||
@@ -160,24 +183,34 @@ expect(count).to.equal(10); | ||
const row = { a: 'foo', b: 1, c: 3.14, d: Buffer.alloc(4).fill(0xdd), e: null }; | ||
const stmt = db.prepare("SELECT * FROM entries"); | ||
db.prepare('SELECT 555'); | ||
const stmt = this.db.prepare("SELECT * FROM entries ORDER BY rowid"); | ||
this.db.prepare('SELECT 555'); | ||
const iterator = stmt.iterate(); | ||
expect(() => db.prepare('SELECT 555')).to.throw(TypeError); | ||
expect(() => this.db.prepare('SELECT 555')).to.throw(TypeError); | ||
expect(iterator.next()).to.deep.equal({ value: row, done: false }); | ||
row.b += 1; | ||
expect(() => db.prepare('SELECT 555')).to.throw(TypeError); | ||
expect(() => this.db.prepare('SELECT 555')).to.throw(TypeError); | ||
expect(iterator.next()).to.deep.equal({ value: row, done: false }); | ||
row.b += 1; | ||
expect(() => db.prepare('SELECT 555')).to.throw(TypeError); | ||
expect(() => this.db.prepare('SELECT 555')).to.throw(TypeError); | ||
expect(iterator.next()).to.deep.equal({ value: row, done: false }); | ||
expect(() => db.prepare('SELECT 555')).to.throw(TypeError); | ||
expect(() => this.db.prepare('SELECT 555')).to.throw(TypeError); | ||
expect(iterator.return()).to.deep.equal({ value: undefined, done: true }); | ||
db.prepare('SELECT 555'); | ||
this.db.prepare('SELECT 555'); | ||
expect(iterator.next()).to.deep.equal({ value: undefined, done: true }); | ||
db.prepare('SELECT 555'); | ||
this.db.prepare('SELECT 555'); | ||
expect(iterator.return()).to.deep.equal({ value: undefined, done: true }); | ||
db.prepare('SELECT 555'); | ||
this.db.prepare('SELECT 555'); | ||
expect(iterator.next()).to.deep.equal({ value: undefined, done: true }); | ||
db.prepare('SELECT 555'); | ||
this.db.prepare('SELECT 555'); | ||
}); | ||
it('should accept bind parameters', function () { | ||
const shouldHave = (SQL, desiredData, args) => { | ||
let i = 0; | ||
const stmt = this.db.prepare(SQL); | ||
for (const data of stmt.iterate(...args)) { | ||
desiredData.b = ++i; | ||
expect(data).to.deep.equal(desiredData); | ||
} | ||
expect(i).to.equal(1); | ||
}; | ||
const row = { a: 'foo', b: 1, c: 3.14, d: Buffer.alloc(4).fill(0xdd), e: null }; | ||
@@ -187,8 +220,8 @@ const SQL1 = 'SELECT * FROM entries WHERE a=? AND b=? AND c=? AND d=? AND e IS ?'; | ||
shouldHave(SQL1, row, ['foo', 1, 3.14, Buffer.alloc(4).fill(0xdd), null]) | ||
shouldHave(SQL1, row, [['foo', 1, 3.14, Buffer.alloc(4).fill(0xdd), null]]) | ||
shouldHave(SQL1, row, [['foo', 1], [3.14], Buffer.alloc(4).fill(0xdd), [,]]) | ||
shouldHave(SQL2, row, [{ a: 'foo', b: 1, c: 3.14, d: Buffer.alloc(4).fill(0xdd), e: undefined }]) | ||
shouldHave(SQL1, row, ['foo', 1, 3.14, Buffer.alloc(4).fill(0xdd), null]); | ||
shouldHave(SQL1, row, [['foo', 1, 3.14, Buffer.alloc(4).fill(0xdd), null]]); | ||
shouldHave(SQL1, row, [['foo', 1], [3.14], Buffer.alloc(4).fill(0xdd), [,]]); | ||
shouldHave(SQL2, row, [{ a: 'foo', b: 1, c: 3.14, d: Buffer.alloc(4).fill(0xdd), e: undefined }]); | ||
for (const data of db.prepare(SQL2).iterate({ a: 'foo', b: 1, c: 3.14, d: Buffer.alloc(4).fill(0xaa), e: undefined })) { | ||
for (const data of this.db.prepare(SQL2).iterate({ a: 'foo', b: 1, c: 3.14, d: Buffer.alloc(4).fill(0xaa), e: undefined })) { | ||
throw new Error('This callback should not have been invoked'); | ||
@@ -198,40 +231,30 @@ } | ||
expect(() => | ||
db.prepare(SQL2).iterate(row, () => {}) | ||
this.db.prepare(SQL2).iterate(row, () => {}) | ||
).to.throw(TypeError); | ||
expect(() => | ||
db.prepare(SQL2).iterate({ a: 'foo', b: 1, c: 3.14, d: Buffer.alloc(4).fill(0xdd) }) | ||
this.db.prepare(SQL2).iterate({ a: 'foo', b: 1, c: 3.14, d: Buffer.alloc(4).fill(0xdd) }) | ||
).to.throw(RangeError); | ||
expect(() => | ||
db.prepare(SQL1).iterate() | ||
this.db.prepare(SQL1).iterate() | ||
).to.throw(RangeError); | ||
expect(() => | ||
db.prepare(SQL2).iterate() | ||
this.db.prepare(SQL2).iterate() | ||
).to.throw(TypeError); | ||
expect(() => | ||
db.prepare(SQL2).iterate(row, {}) | ||
this.db.prepare(SQL2).iterate(row, {}) | ||
).to.throw(TypeError); | ||
expect(() => | ||
db.prepare(SQL2).iterate({}) | ||
this.db.prepare(SQL2).iterate({}) | ||
).to.throw(RangeError); | ||
db.prepare(SQL1).iterate('foo', 1, 3.14, Buffer.alloc(4).fill(0xdd), null).return(); | ||
this.db.prepare(SQL1).iterate('foo', 1, 3.14, Buffer.alloc(4).fill(0xdd), null).return(); | ||
expect(() => | ||
db.prepare(SQL1).iterate('foo', 1, new (function(){})(), Buffer.alloc(4).fill(0xdd), null) | ||
this.db.prepare(SQL1).iterate('foo', 1, new (function(){})(), Buffer.alloc(4).fill(0xdd), null) | ||
).to.throw(TypeError); | ||
function shouldHave(SQL, desiredData, args) { | ||
let i = 0; | ||
const stmt = db.prepare(SQL); | ||
for (const data of stmt.iterate(...args)) { | ||
desiredData.b = ++i; | ||
expect(data).to.deep.equal(desiredData); | ||
} | ||
expect(i).to.equal(1); | ||
} | ||
}); | ||
}); |
'use strict'; | ||
const { expect } = require('chai'); | ||
const Database = require('../.'); | ||
const db = new Database(require('./util').next()); | ||
describe('Statement#bind()', function () { | ||
beforeEach(function () { | ||
this.db = new Database(util.next()); | ||
this.db.prepare('CREATE TABLE entries (a TEXT, b INTEGER, c BLOB)').run(); | ||
}); | ||
afterEach(function () { | ||
this.db.close(); | ||
}); | ||
it('should permanently bind the given parameters', function () { | ||
db.prepare('CREATE TABLE entries (a TEXT, b INTEGER, c BLOB)').run(); | ||
const stmt = db.prepare('INSERT INTO entries VALUES (?, ?, ?)'); | ||
const stmt = this.db.prepare('INSERT INTO entries VALUES (?, ?, ?)'); | ||
const buffer = Buffer.alloc(4).fill(0xdd); | ||
@@ -15,4 +20,4 @@ stmt.bind('foobar', 25, buffer) | ||
stmt.run(); | ||
const row1 = db.prepare('SELECT * FROM entries WHERE rowid=1').get(); | ||
const row2 = db.prepare('SELECT * FROM entries WHERE rowid=2').get(); | ||
const row1 = this.db.prepare('SELECT * FROM entries WHERE rowid=1').get(); | ||
const row2 = this.db.prepare('SELECT * FROM entries WHERE rowid=2').get(); | ||
expect(row1.a).to.equal(row2.a); | ||
@@ -23,3 +28,3 @@ expect(row1.b).to.equal(row2.b); | ||
it('should not allow you to bind temporary parameters afterwards', function () { | ||
const stmt = db.prepare('INSERT INTO entries VALUES (?, ?, ?)'); | ||
const stmt = this.db.prepare('INSERT INTO entries VALUES (?, ?, ?)'); | ||
const buffer = Buffer.alloc(4).fill(0xdd); | ||
@@ -32,3 +37,3 @@ stmt.bind('foobar', 25, buffer) | ||
it('should throw an exception when invoked twice on the same statement', function () { | ||
let stmt = db.prepare('INSERT INTO entries VALUES (?, ?, ?)'); | ||
let stmt = this.db.prepare('INSERT INTO entries VALUES (?, ?, ?)'); | ||
stmt.bind('foobar', 25, null); | ||
@@ -38,3 +43,3 @@ expect(() => stmt.bind('foobar', 25, null)).to.throw(TypeError); | ||
stmt = db.prepare('SELECT * FROM entries'); | ||
stmt = this.db.prepare('SELECT * FROM entries'); | ||
stmt.bind(); | ||
@@ -44,3 +49,3 @@ expect(() => stmt.bind()).to.throw(TypeError); | ||
it('should throw an exception when invalid parameters are given', function () { | ||
let stmt = db.prepare('INSERT INTO entries VALUES (?, ?, ?)'); | ||
let stmt = this.db.prepare('INSERT INTO entries VALUES (?, ?, ?)'); | ||
@@ -65,3 +70,3 @@ expect(() => | ||
stmt = db.prepare('INSERT INTO entries VALUES (@a, @a, ?)'); | ||
stmt = this.db.prepare('INSERT INTO entries VALUES (@a, @a, ?)'); | ||
@@ -82,3 +87,3 @@ expect(() => | ||
stmt = db.prepare('INSERT INTO entries VALUES (@a, @a, ?)'); | ||
stmt = this.db.prepare('INSERT INTO entries VALUES (@a, @a, ?)'); | ||
stmt.bind({ a: '123', b: null }, null); | ||
@@ -92,4 +97,4 @@ }); | ||
Object.defineProperty(obj, 'baz', { get: () => { throw err; } }) | ||
const stmt1 = db.prepare('SELECT ?'); | ||
const stmt2 = db.prepare('SELECT @baz'); | ||
const stmt1 = this.db.prepare('SELECT ?'); | ||
const stmt2 = this.db.prepare('SELECT @baz'); | ||
expect(() => stmt1.bind(arr)).to.throw(err); | ||
@@ -96,0 +101,0 @@ expect(() => stmt2.bind(obj)).to.throw(err); |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
73
2822
67
2510218
9
4
+ Addedtar@^4.4.6
+ Addedchownr@1.1.4(transitive)
+ Addedfs-minipass@1.2.7(transitive)
+ Addedinteger@2.1.0(transitive)
+ Addedminimist@1.2.8(transitive)
+ Addedminipass@2.9.0(transitive)
+ Addedminizlib@1.3.3(transitive)
+ Addedmkdirp@0.5.6(transitive)
+ Addedsafe-buffer@5.2.1(transitive)
+ Addedtar@4.4.19(transitive)
+ Addedyallist@3.1.1(transitive)
- Removedbindings@^1.3.0
- Removedbindings@1.5.0(transitive)
- Removedfile-uri-to-path@1.0.0(transitive)
- Removedinteger@1.0.7(transitive)
Updatedinteger@^2.1.0