icql-dba
Version:
An SQLite DB Adapter built with BetterSQLite3
1,014 lines (922 loc) • 42.2 kB
JavaScript
(function() {
'use strict';
var CND, Checks_mixin, E, FS, Functions_mixin, Import_export_mixin, L, PATH, SQL, Stdlib_mixin, TMP, badge, debug, echo, freeze, guy, help, info, lets, misfit, new_bsqlt3_connection, ref1, rpr, types, urge, warn, whisper,
indexOf = [].indexOf,
boundMethodCheck = function(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new Error('Bound instance method accessed before binding'); } };
//###########################################################################################################
CND = require('cnd');
rpr = CND.rpr;
badge = 'ICQL/DBA';
debug = CND.get_logger('debug', badge);
warn = CND.get_logger('warn', badge);
info = CND.get_logger('info', badge);
urge = CND.get_logger('urge', badge);
help = CND.get_logger('help', badge);
whisper = CND.get_logger('whisper', badge);
echo = CND.echo.bind(CND);
//...........................................................................................................
FS = require('fs');
//...........................................................................................................
types = require('./types');
({freeze, lets} = require('letsfreezethat'));
L = this;
({misfit} = require('./common'));
E = require('./errors');
new_bsqlt3_connection = require('better-sqlite3');
PATH = require('path');
TMP = require('tempy');
({Import_export_mixin} = require('./import-export-mixin'));
({Functions_mixin} = require('./functions-mixin'));
({Checks_mixin} = require('./checks-mixin'));
({Stdlib_mixin} = require('./stdlib-mixin'));
guy = require('guy');
SQL = String.raw;
//===========================================================================================================
//-----------------------------------------------------------------------------------------------------------
ref1 = this.Dba = (function() {
class Dba extends Stdlib_mixin(Checks_mixin(Functions_mixin(Import_export_mixin()))) {
//---------------------------------------------------------------------------------------------------------
constructor(cfg) {
super();
//---------------------------------------------------------------------------------------------------------
this._get_connection_url = this._get_connection_url.bind(this);
guy.props.def(this, 'types', {
enumerable: false,
value: types
});
guy.props.def(this, '_statements', {
enumerable: false,
value: {}
});
guy.props.def(this, 'sql', {
enumerable: false,
value: new (require('./sql')).Sql()
});
this._schemas = freeze({});
this.cfg = freeze({...this.types.defaults.dba_constructor_cfg, ...cfg});
this.types.validate.dba_constructor_cfg(this.cfg);
this._dbg = {
debug: this.cfg.debug,
echo: this.cfg.echo
};
this._formats = freeze({...this.types.defaults.extensions_and_formats});
if (this.cfg.sqlt != null) {
throw new E.Dba_cfg_error('^dba@300^', "property `sqlt` not supported");
}
if (this.cfg.schema != null) {
throw new E.Dba_cfg_error('^dba@301^', "property `schema` not supported");
}
if (this.cfg.path != null) {
throw new E.Dba_cfg_error('^dba@302^', "property `path` not supported");
}
this._bsqlt3_cfg = freeze({
readonly: this.cfg.readonly,
fileMustExist: !this.cfg.create,
timeout: this.cfg.timeout
});
// verbose: ### TAINT to be done ###
this._state = {
in_unsafe_mode: false,
initialized: false,
stdlib_created: false
};
this._catalog = freeze({});
//.......................................................................................................
/* NOTE: will hold data on user-defined functions, virtual tables */ guy.props.def_oneoff(this, 'sqlt', {}, () => {
var connection;
connection = new_bsqlt3_connection('', this._bsqlt3_cfg);
this.initialize_sqlt(connection);
this._state.initialized = true;
return connection;
});
//.......................................................................................................
return void 0;
}
// #---------------------------------------------------------------------------------------------------------
// constructor: ( cfg ) ->
// super()
// guy.props.def @, 'types', { enumerable: false, value: types, }
// guy.props.def @, '_statements', { enumerable: false, value: {}, }
// guy.props.def @, 'sql', { enumerable: false, value: ( new ( require './sql' ).Sql() ), }
// @_schemas = freeze {}
// @cfg = freeze { @types.defaults.dba_constructor_cfg..., cfg..., }
// @types.validate.dba_constructor_cfg @cfg
// @_dbg = { debug: @cfg.debug, echo: @cfg.echo, }
// @_formats = freeze { @types.defaults.extensions_and_formats..., }
// # throw new E.Dba_cfg_error '^dba@300^', "property `sqlt` not supported" if @cfg.sqlt?
// throw new E.Dba_cfg_error '^dba@300^', "property `schema` not supported" if @cfg.schema?
// @_bsqlt3_cfg = freeze {
// readonly: @cfg.readonly
// fileMustExist: not @cfg.create
// timeout: @cfg.timeout }
// # verbose: ### TAINT to be done ###
// @_state = {
// in_unsafe_mode: false
// initialized: false
// stdlib_created: false }
// @_catalog = freeze {} ### NOTE: will hold data on user-defined functions, virtual tables ###
// @_state.initialized = true ### TAINT remove ###
// @_initialize_prng()
// #.......................................................................................................
// { ram
// name
// path } = @cfg
// if ram
// ### formulate connection string à la 'file:memdb1?mode=memory&cache=shared' ###
// { url
// name } = dba._get_connection_url name ? null
// debug '^3443^', name, url
// if path?
// ### File DB, Eventual Persistency; opened from file (may get created), mirror to RAM ###
// null
// else
// ### RAM DB, No Persistency, starts empty ###
// null
// else
// ### File DB, Continuous Persistency ###
// null
// @sqlt = new_bsqlt3_connection path, @_bsqlt3_cfg
// @sqlt2 = new_bsqlt3_connection path, @_bsqlt3_cfg
// @initialize_sqlt @sqlt
// #.......................................................................................................
// return undefined
//---------------------------------------------------------------------------------------------------------
initialize_sqlt(sqlt) {
/* NOTE do NOT use any instance methods in the initializer as this will lead to infinite regress b/c of
the way the dynamic attribute `@sqlt` has been implemented. */
sqlt.pragma("foreign_keys = true;");
return null;
}
//---------------------------------------------------------------------------------------------------------
open(cfg) {
var path, ram, saveas, schema;
this.types.validate.dba_open_cfg((cfg = {...this.types.defaults.dba_open_cfg, ...cfg}));
({path, schema, ram} = cfg);
//.......................................................................................................
/* TAINT troublesome logic with `path` and `saveas` */
if (path != null) {
saveas = path;
} else {
path = ''/* TAINT or ':memory:' depending on `cfg.disk` */
saveas = null;
}
//.......................................................................................................
if (ram) {
this._open_file_db_in_ram({path, schema, saveas});
} else {
this._attach({path, schema, saveas});
}
//.......................................................................................................
return null;
}
//---------------------------------------------------------------------------------------------------------
save(cfg) {
var path, ref2, ref3, schema;
/* TAINT could implement prohibition of `path` in type `dba_save_cfg` */
this.types.validate.dba_save_cfg((cfg = {...this.types.defaults.dba_export_cfg, ...cfg}));
({schema, path} = cfg);
if (path != null) {
throw new E.Dba_argument_not_allowed('^dba@303^', 'path', path);
}
path = (ref2 = (ref3 = this._schemas[schema]) != null ? ref3.path : void 0) != null ? ref2 : null;
if (path == null) {
throw new E.Dba_schema_unknown('^dba@304^', schema);
}
return this.export({
schema,
path,
format: 'sqlite'
});
}
//---------------------------------------------------------------------------------------------------------
export(cfg) {
var format, path, schema;
/* TAINT add boolean `cfg.overwrite` */
this.types.validate.dba_export_cfg((cfg = {...this.types.defaults.dba_export_cfg, ...cfg}));
({schema, path, format} = cfg);
if (format == null) {
format = this._format_from_path(path);
}
if (format == null) {
throw new E.Dba_extension_unknown('^dba@305^', path);
}
switch (format) {
case 'sqlite':
this._vacuum_atomically({schema, path});
break;
default:
/* TAINT when format derived from path, may be undefined, making the error message unintelligible */
throw new E.Dba_format_unknown('^dba@306^', format);
}
return null;
}
//---------------------------------------------------------------------------------------------------------
_vacuum_atomically(cfg) {
var path, schema, schema_i, tmpdir_path, tmpfile_path;
this.types.validate.dba_vacuum_atomically((cfg = {...this.types.defaults.dba_vacuum_atomically, ...cfg}));
({schema, path} = cfg);
schema_i = this.sql.I(schema);
try {
tmpdir_path = TMP.directory({
prefix: this.cfg._temp_prefix
});
tmpfile_path = PATH.join(tmpdir_path, PATH.basename(path));
this.run(`vacuum ${schema_i} into ?;`, [tmpfile_path]);
FS.renameSync(tmpfile_path, path);
} finally {
FS.rmdirSync(tmpdir_path);
}
return null;
}
//---------------------------------------------------------------------------------------------------------
is_ram_db(cfg) {
var error, schema, sql;
this.types.validate.dba_is_ram_db_cfg((cfg = {...this.types.defaults.dba_is_ram_db_cfg, ...cfg}));
({schema} = cfg);
sql = "select file from pragma_database_list where name = ? limit 1;";
try {
return this.types.isa.dba_ram_path(this.single_value(this.query(sql, [schema])));
} catch (error1) {
error = error1;
if (error instanceof E.Dba_expected_one_row) {
throw new E.Dba_schema_unknown('^dba@307^', schema);
}
throw error;
}
}
//---------------------------------------------------------------------------------------------------------
_list_temp_schema_numbers() {
var matcher, sql;
matcher = this.cfg._temp_prefix + '%';
sql = `select
cast( substring( name, ? ) as integer ) as n
from pragma_database_list
where name like ?;`;
return this.all_first_values(this.query(sql, [this.cfg._temp_prefix.length + 1, matcher]));
}
//---------------------------------------------------------------------------------------------------------
_max_temp_schema_number() {
var matcher, ref2, sql;
matcher = this.cfg._temp_prefix + '%';
sql = `select
max( cast( substring( name, ? ) as integer ) ) as n
from pragma_database_list
where name like ?;`;
return (ref2 = this.first_value(this.query(sql, [this.cfg._temp_prefix.length + 1, matcher]))) != null ? ref2 : 0;
}
//---------------------------------------------------------------------------------------------------------
_get_free_temp_schema() {
return this.cfg._temp_prefix + `${this._max_temp_schema_number() + 1}`;
}
//=========================================================================================================
// DEBUGGING
//---------------------------------------------------------------------------------------------------------
_echo(ref, sql) {
if (!this._dbg.echo) {
return null;
}
echo((CND.reverse(CND.blue(`^icql@888-${ref}^`))) + (CND.reverse(CND.yellow(sql))));
return null;
}
//---------------------------------------------------------------------------------------------------------
_debug(...P) {
if (!this._dbg.debug) {
return null;
}
debug(...P);
return null;
}
//=========================================================================================================
// QUERY RESULT ADAPTERS
//---------------------------------------------------------------------------------------------------------
* limit(n, iterator) {
var count, x;
count = 0;
for (x of iterator) {
if (count >= n) {
return;
}
count += +1;
yield x;
}
}
//---------------------------------------------------------------------------------------------------------
single_row(iterator) {
var R;
if ((R = this.first_row(iterator)) === void 0) {
throw new E.Dba_expected_one_row('dba@763^', 0);
}
return R;
}
//---------------------------------------------------------------------------------------------------------
all_first_values(iterator) {
var R, key, row, value;
R = [];
for (row of iterator) {
for (key in row) {
value = row[key];
R.push(value);
break;
}
}
return R;
}
//---------------------------------------------------------------------------------------------------------
* first_values(iterator) {
var R, key, row, value;
R = [];
for (row of iterator) {
for (key in row) {
value = row[key];
yield value;
}
}
return R;
}
//---------------------------------------------------------------------------------------------------------
first_row(iterator) {
var row;
for (row of iterator) {
return row;
}
}
/* TAINT must ensure order of keys in row is same as order of fields in query */
single_value(iterator) {
var key, ref2, value;
ref2 = this.single_row(iterator);
for (key in ref2) {
value = ref2[key];
return value;
}
}
first_value(iterator) {
var key, ref2, value;
ref2 = this.first_row(iterator);
for (key in ref2) {
value = ref2[key];
return value;
}
}
list(iterator) {
return [...iterator];
}
//=========================================================================================================
// QUERYING
//---------------------------------------------------------------------------------------------------------
query(sql, ...P) {
var base, statement;
this._echo('query', sql);
statement = ((base = this._statements)[sql] != null ? base[sql] : base[sql] = this.sqlt.prepare(sql));
return statement.iterate(...P);
}
//---------------------------------------------------------------------------------------------------------
run(sql, ...P) {
var base, statement;
this._echo('run', sql);
statement = ((base = this._statements)[sql] != null ? base[sql] : base[sql] = this.sqlt.prepare(sql));
return statement.run(...P);
}
//---------------------------------------------------------------------------------------------------------
_run_or_query(entry_type, is_last, sql, Q) {
var base, returns_data, statement;
this._echo('_run_or_query', sql);
statement = ((base = this._statements)[sql] != null ? base[sql] : base[sql] = this.sqlt.prepare(sql));
returns_data = statement.reader;
//.......................................................................................................
/* Always use `run()` method if statement does not return data: */
if (!returns_data) {
if (Q != null) {
return statement.run(Q);
} else {
return statement.run();
}
}
//.......................................................................................................
/* If statement does return data, consume iterator unless this is the last statement: */
if ((entry_type === 'procedure') || (!is_last)) {
if (Q != null) {
return statement.all(Q);
} else {
return statement.all();
}
}
//.......................................................................................................
/* Return iterator: */
if (Q != null) {
return statement.iterate(Q);
} else {
return statement.iterate();
}
}
//---------------------------------------------------------------------------------------------------------
execute(sql) {
var x;
if ((x = arguments[1]) != null) {
throw new E.Dba_argument_not_allowed('^dba@308^', "extra", rpr(x));
}
this._echo('execute', sql);
return this.sqlt.exec(sql);
}
//---------------------------------------------------------------------------------------------------------
prepare(sql) {
this._echo('prepare', sql);
return this.sqlt.prepare(sql);
}
//=========================================================================================================
// OTHER
//---------------------------------------------------------------------------------------------------------
backup(...P) {
return this.sqlt.backup(...P);
}
checkpoint(...P) {
return this.sqlt.checkpoint(...P);
}
close(...P) {
return this.sqlt.close(...P);
}
read(path) {
return this.sqlt.exec(FS.readFileSync(path, {
encoding: 'utf-8'
}));
}
load_extension(...P) {
return this.sqlt.loadExtension(...P);
}
pragma(...P) {
return this.sqlt.pragma(...P);
}
//=========================================================================================================
// DB STRUCTURE REPORTING
//---------------------------------------------------------------------------------------------------------
catalog() {
/* TAINT kludge: we sort by descending types so views, tables come before indexes (b/c you can't drop a
primary key index in SQLite) */
throw new E.Dba_not_implemented('^dba@309^', "method dba.catalog()");
return this.query("select * from sqlite_schema order by type desc, name;");
}
//---------------------------------------------------------------------------------------------------------
walk_objects(cfg) {
var ordering, ordering_x, schema, schema_i, seq;
this.types.validate.dba_walk_objects_cfg((cfg = {...this.types.defaults.dba_walk_objects_cfg, ...cfg}));
schema = cfg.schema;
ordering = cfg._ordering;
if (schema == null) {
return this._walk_all_objects();
}
schema_i = this.sql.I(schema);
ordering_x = ordering === 'drop' ? 'desc' : 'asc';
seq = this.first_value(this.query(this.sql.SQL`select seq from pragma_database_list where name = ${this.sql.L(schema)};`));
//.......................................................................................................
return this.query(this.sql.SQL`select
${seq} as seq,
${this.sql.L(schema)} as schema,
name as name,
type as type,
sql as sql
from ${this.sql.I(schema)}.sqlite_schema
order by seq, type ${ordering_x}, name;`);
}
//---------------------------------------------------------------------------------------------------------
_walk_all_objects() {
var d, parts, ref2, row, schema, schema_i, schema_l, schemas, sql;
schemas = {};
parts = [];
ref2 = this.query(SQL`select seq, name, file as path from pragma_database_list order by seq;`);
//.......................................................................................................
/* TAINT use API */
for (row of ref2) {
schemas[row.name] = row;
}
//.......................................................................................................
for (schema in schemas) {
d = schemas[schema];
schema_i = this.sql.I(schema);
schema_l = this.sql.L(schema);
parts.push(SQL`select
${d.seq} as seq,
${schema_l} as schema,
name as name,
type as type,
sql as sql
from ${schema_i}.sqlite_schema as d1`);
}
parts = parts.join(" union all\n");
//.......................................................................................................
sql = '';
sql += parts;
sql += "\norder by seq, type, name;";
return this.query(sql);
}
//---------------------------------------------------------------------------------------------------------
is_empty(cfg) {
var has_schema;
this.types.validate.dba_is_empty_cfg((cfg = {...this.types.defaults.dba_is_empty_cfg, ...cfg}));
if (typeof name === "undefined" || name === null) {
return (has_schema = this._is_empty_schema(this.sql.I(cfg.schema)));
}
throw new E.Dba_not_implemented('^dba@310^', `dba.is_empty() for anything but schemas (got ${rpr(cfg)})`);
}
//---------------------------------------------------------------------------------------------------------
_is_empty_schema(schema_i) {
return (this.list(this.query(`select 1 from ${schema_i}.sqlite_schema limit 1;`))).length === 0;
}
//---------------------------------------------------------------------------------------------------------
list_schemas() {
return this.list(this.query("select * from pragma_database_list order by name;"));
}
list_schema_names() {
var d, i, len, ref2, results;
ref2 = this.list_schemas();
results = [];
for (i = 0, len = ref2.length; i < len; i++) {
d = ref2[i];
results.push(d.name);
}
return results;
}
//---------------------------------------------------------------------------------------------------------
has(cfg) {
var ref2;
this.types.validate.dba_has_cfg((cfg = {...this.types.defaults.dba_has_cfg, ...cfg}));
return ref2 = cfg.schema, indexOf.call(this.list_schema_names(), ref2) >= 0;
}
//---------------------------------------------------------------------------------------------------------
get_schemas() {
var R, ref2, row;
R = {};
ref2 = this.query("select * from pragma_database_list order by seq;");
for (row of ref2) {
R[row.name] = row.file;
}
return R;
}
//---------------------------------------------------------------------------------------------------------
_path_of_schema(schema, fallback = misfit) {
var R;
R = this.first_value(this.query("select file from pragma_database_list where name = ?;", [schema]));
if (R != null) {
return R;
}
if (fallback !== misfit) {
return fallback;
}
throw new E.Dba_schema_unknown('^dba@311^', schema);
}
//---------------------------------------------------------------------------------------------------------
type_of(cfg) {
var name, row, schema;
this.types.validate.dba_type_of_cfg((cfg = {...this.types.defaults.dba_type_of_cfg, ...cfg}));
({name, schema} = cfg);
if (name === 'sqlite_schema' || name === 'sqlite_master') {
return 'table';
}
row = this.first_row(this.query(SQL`select type from ${this.sql.I(schema)}.sqlite_schema
where name = $name
limit 1;`, {name}));
if (row == null) {
throw new E.Dba_object_unknown('^dba@311^', schema, name);
}
return row.type;
}
//---------------------------------------------------------------------------------------------------------
fields_of(cfg) {
var R, d, name, ref2, schema, schema_i, type;
this.types.validate.dba_fields_of_cfg((cfg = {...this.types.defaults.dba_fields_of_cfg, ...cfg}));
({name, schema} = cfg);
schema_i = this.sql.I(schema);
R = {};
ref2 = this.query(SQL`select * from ${schema_i}.pragma_table_info( $name );`, {name});
for (d of ref2) {
// { cid: 0, name: 'id', type: 'integer', notnull: 1, dflt_value: null, pk: 1 }
type = d.type === '' ? null : d.type;
R[d.name] = {
idx: d.cid,
type: type,
optional: !d.notnull,
default: d.dflt_value,
is_pk: !!d.pk
};
}
return R;
}
//---------------------------------------------------------------------------------------------------------
field_names_of(cfg) {
var d, name, schema, schema_i;
// try
this.types.validate.dba_field_names_of_cfg((cfg = {...this.types.defaults.dba_field_names_of_cfg, ...cfg}));
({name, schema} = cfg);
schema_i = this.sql.I(schema);
return (function() {
var ref2, results;
ref2 = this.query(SQL`select name from ${schema_i}.pragma_table_info( $name );`, {name});
results = [];
for (d of ref2) {
results.push(d.name);
}
return results;
}).call(this);
}
// catch error
// throw new E.Dba_sqlite_error '^dba@111^', error
//---------------------------------------------------------------------------------------------------------
dump_relation(cfg) {
var limit, name, order_by, qname_i, schema, schema_i;
this.types.validate.dba_dump_relation_cfg((cfg = {...this.types.defaults.dba_dump_relation_cfg, ...cfg}));
({schema, name, order_by, limit} = cfg);
schema_i = this.sql.I(schema);
qname_i = schema_i + '.' + this.sql.I(name);
limit = cfg.limit === null ? 1e9 : cfg.limit;
if (order_by == null) {
order_by = 'random()';
}
return this.query(SQL`select * from ${qname_i} order by ${order_by} limit ${limit};`);
}
//---------------------------------------------------------------------------------------------------------
_dependencies_of(table, schema = 'main') {
return this.query(`pragma ${this.sql.I(schema)}.foreign_key_list( ${this.sql.I(table)} )`);
}
//---------------------------------------------------------------------------------------------------------
dependencies_of(table, schema = 'main') {
var row;
this.types.validate.ic_schema(schema);
return (function() {
var ref2, results;
ref2 = this._dependencies_of(table);
results = [];
for (row of ref2) {
results.push(row.table);
}
return results;
}).call(this);
}
//=========================================================================================================
// DB STRUCTURE MODIFICATION
//---------------------------------------------------------------------------------------------------------
/* TAINT Error: index associated with UNIQUE or PRIMARY KEY constraint cannot be dropped */
clear(cfg) {
var R, fk_state, i, len, name, ref2, schema, schema_i, statement, type;
this.types.validate.dba_clear_cfg((cfg = {...this.types.defaults.dba_clear_cfg, ...cfg}));
({schema} = cfg);
schema_i = this.sql.I(schema);
R = 0;
fk_state = this.get_foreign_keys_state();
this.set_foreign_keys_state(false);
ref2 = this.list(this.walk_objects({
schema,
_ordering: 'drop'
}));
for (i = 0, len = ref2.length; i < len; i++) {
({type, name} = ref2[i]);
statement = `drop ${type} if exists ${this.sql.I(name)};`;
this.execute(statement);
R += +1;
}
this.set_foreign_keys_state(fk_state);
return R;
}
//---------------------------------------------------------------------------------------------------------
_open_file_db_in_ram(cfg) {
/* Given a `path` and a `schema`, create a temporary schema to open the file DB in as well as an empty
in-memory schema; then copy all DB objects and their contents from the temporary file schema to the RAM
schema. Finally, detach the file schema. Ensure the `path` given is kept around as the `saveas`
(implicit) path to be used for eventual persistency (`dba.save()`). */
/* TAINT validate? */
var path, saveas, schema, schema_main_allowed, tmp_schema;
schema_main_allowed = !this._state.initialized;
({path, schema, saveas} = cfg);
if (this.types.isa.dba_ram_path(path)) {
return this._attach({schema, path, saveas});
}
//.......................................................................................................
tmp_schema = this._get_free_temp_schema();
this._attach({
schema: tmp_schema,
path
});
if (!((schema === 'main') && schema_main_allowed)) {
this._attach({
schema,
path: '',
saveas
});
}
this._copy_schema({
from_schema: tmp_schema,
to_schema: schema
});
this._detach({
schema: tmp_schema
});
//.......................................................................................................
this._schemas = lets(this._schemas, (d) => {
return d[schema] = {
path: saveas
};
});
/* TAINT use API call */ return null;
}
//---------------------------------------------------------------------------------------------------------
_attach(cfg) {
/* TAINT code duplication from oneoff handler */
var connection, error, ignore, path, saveas, schema;
/* Given a `path` and a `schema`, execute SQL"attach $path as $schema".
`_attach()` will fail
* if `schema` already exists, or
* if the maximum number of schemas (10 by default) has already been attached, or
* if the schema name is `main` and the DBA is `@_state.initialized`.
If `@_state.initialized` is `false`, then a new `better-sqlite3` instance with a `main` schema will be
created;
* if the `schema` passed in is `main`, it will be opened from the `path` given.
* If `schema` is not `main`, `main` will be opened as an empty RAM DB, and `schema` will be attached
from the file given.
*/
this.types.validate.dba_attach_cfg((cfg = {...this.types.defaults.dba_attach_cfg, ...cfg}));
({path, schema, saveas} = cfg);
//.......................................................................................................
if (!this._state.initialized) {
if (schema === 'main') {
connection = new_bsqlt3_connection(path, this._bsqlt3_cfg);
this.initialize_sqlt(connection);
guy.props.def(this, 'sqlt', {
enumerable: false,
configurable: false,
value: connection
});
this._state.initialized = true;
this._schemas = lets(this._schemas, (d) => {
return d[schema] = {
path: saveas
};
});
/* TAINT use API call */ return null;
}
ignore = this.sqlt/* NOTE retrieve dynamic attribute for side effect, ignore its value */
}
//.......................................................................................................
if (this.has({schema})) {
throw new E.Dba_schema_exists('^dba@312^', schema);
}
try {
//.......................................................................................................
this.run("attach ? as ?;", [path, schema]);
} catch (error1) {
error = error1;
if (error.code !== 'SQLITE_ERROR') {
throw error;
}
if (error.message.startsWith('too many attached databases')) {
throw new E.Dba_sqlite_too_many_dbs('^dba@313^', schema);
}
throw new E.Dba_sqlite_error('^dba@314^', error);
}
this._schemas = lets(this._schemas, (d) => {
return d[schema] = {
path: saveas
};
});
/* TAINT use API call */ return null;
}
//---------------------------------------------------------------------------------------------------------
_detach(cfg) {
this.types.validate.dba_detach_cfg((cfg = {...this.types.defaults.dba_detach_cfg, ...cfg}));
this.execute(this.sql.SQL`detach ${this.sql.I(cfg.schema)};`);
this._schemas = lets(this._schemas, (d) => {
return delete d[cfg.schema];
});
return null;
}
//=========================================================================================================
// IN-MEMORY PROCESSING
//-----------------------------------------------------------------------------------------------------------
_move_schema(cfg) {
return this._copy_or_move_schema(cfg, true);
}
_copy_schema(cfg) {
return this._copy_or_move_schema(cfg, false);
}
//-----------------------------------------------------------------------------------------------------------
_copy_or_move_schema(cfg, detach_schema = false) {
var d, detach_from_schema, fk_state, from_schema, from_schema_objects, from_schema_x, i, inserts, j, known_schemas, len, len1, name_x, ref2, ref3, sql, to_schema, to_schema_objects, to_schema_x;
detach_from_schema = function() {
if (!detach_schema) {
return null;
}
return this._detach({
schema: from_schema
});
};
//.......................................................................................................
this.types.validate.copy_or_move_schema_cfg((cfg = {...this.types.defaults.copy_or_move_schema_cfg, ...cfg}));
({from_schema, to_schema} = cfg);
//.......................................................................................................
if (from_schema === to_schema) {
throw new E.Dba_schema_repeated('^dba@315^', from_schema);
}
//.......................................................................................................
known_schemas = this.list_schema_names();
if (indexOf.call(known_schemas, from_schema) < 0) {
throw new E.Dba_schema_unknown('^dba@316^', from_schema);
}
if (indexOf.call(known_schemas, to_schema) < 0) {
throw new E.Dba_schema_unknown('^dba@317^', to_schema);
}
//.......................................................................................................
to_schema_objects = this.list(this.walk_objects({
schema: to_schema
}));
if (to_schema_objects.length > 0) {
throw new E.Dba_schema_nonempty('^dba@318^', to_schema);
}
//.......................................................................................................
from_schema_objects = this.list(this.walk_objects({
schema: from_schema
}));
if (from_schema_objects.length === 0) {
return detach_from_schema();
}
//.......................................................................................................
to_schema_x = this.sql.I(to_schema);
from_schema_x = this.sql.I(from_schema);
inserts = [];
fk_state = this.get_foreign_keys_state();
this.set_foreign_keys_state(false);
//.......................................................................................................
for (i = 0, len = from_schema_objects.length; i < len; i++) {
d = from_schema_objects[i];
if ((d.sql == null) || (d.sql === '')) {
continue;
}
if ((ref2 = d.name) === 'sqlite_sequence') {
continue;
}
//.....................................................................................................
/* TAINT consider to use `validate.ic_db_object_type` */
if ((ref3 = d.type) !== 'table' && ref3 !== 'view' && ref3 !== 'index') {
throw new E.Dba_unexpected_db_object_type('^dba@319^', d.type, d);
}
//.....................................................................................................
/* TAINT using not-so reliable string replacement as substitute for proper parsing */
name_x = this.sql.I(d.name);
sql = d.sql.replace(/\s*CREATE\s*(TABLE|INDEX|VIEW)\s*/i, `create ${d.type} ${to_schema_x}.`);
//.....................................................................................................
if (sql === d.sql) {
throw new E.Dba_unexpected_sql('^dba@320^', d.sql);
}
//.....................................................................................................
this.execute(sql);
if (d.type === 'table') {
inserts.push(`insert into ${to_schema_x}.${name_x} select * from ${from_schema_x}.${name_x};`);
}
}
for (j = 0, len1 = inserts.length; j < len1; j++) {
sql = inserts[j];
//.......................................................................................................
this.execute(sql);
}
this.set_foreign_keys_state(fk_state);
if (fk_state) {
this.pragma(`${this.sql.I(to_schema)}.foreign_key_check;`);
}
return detach_from_schema();
}
_initialize_prng() {
var clasz, delta, ref2, ref3, seed;
clasz = this.constructor;
if (clasz._rnd_int_cfg != null) {
seed = (ref2 = clasz._rnd_int_cfg.seed) != null ? ref2 : 12.34;
delta = (ref3 = clasz._rnd_int_cfg.delta) != null ? ref3 : 1;
this._rnd_int = CND.get_rnd_int(seed, delta);
} else {
this._rnd_int = CND.random_integer.bind(CND);
}
return null;
}
_get_connection_url(dbnick = null) {
var url;
boundMethodCheck(this, ref1);
/* TAINT rename `dbnick` to `dbnick` */
/* Given an optional `dbnick`, return an object with the `dbnick` and the `url` for a new SQLite
connection. The url will look like `'file:your_name_here?mode=memory&cache=shared` so multiple
connections to the same RAM DB can be opened. When `dbnick` is not given, a random dbnick like
`_icql_6200294332` will be chosen (prefix `_icql_`, suffix ten decimal digits). For testing, setting
class property `@_rnd_int_cfg` can be used to obtain repeatable series of random names. */
if (dbnick == null) {
dbnick = `_icql_${this._rnd_int(1_000_000_000, 9_999_999_999)}`;
}
url = `file:${dbnick}?mode=memory&cache=shared`;
return {url, dbnick};
}
};
//=========================================================================================================
// RANDOM NUMBER GENERATION
// seedable for testing purposes
//---------------------------------------------------------------------------------------------------------
/* To obtain a class with a seedable PRNG that emits repeatable sequences, define class property
`@_rnd_int_cfg: { seed, delta, }` where both seed and delta can be arbitrary finite numbers. **NOTE**
very small `delta` values (like 1e-10) may cause adjacent numbers to be close together or even repeat. To
use default values for both parameters, set `@_rnd_int_cfg: true`.*/
Dba._rnd_int_cfg = null;
return Dba;
}).call(this);
}).call(this);
//# sourceMappingURL=main.js.map