icql-dba
Version:
An SQLite DB Adapter built with BetterSQLite3
665 lines (600 loc) • 33.7 kB
text/coffeescript
'use strict'
############################################################################################################
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 = @
{ 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
#===========================================================================================================
#
#-----------------------------------------------------------------------------------------------------------
class @Dba extends Stdlib_mixin Checks_mixin Functions_mixin Import_export_mixin()
#---------------------------------------------------------------------------------------------------------
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@301^', "property `schema` not supported" if @cfg.schema?
throw new E.Dba_cfg_error '^dba@302^', "property `path` not supported" if @cfg.path?
@_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 ###
#.......................................................................................................
guy.props.def_oneoff @, 'sqlt', {}, =>
connection = new_bsqlt3_connection '', @_bsqlt3_cfg
@initialize_sqlt connection
@_state.initialized = true
return connection
#.......................................................................................................
return undefined
# #---------------------------------------------------------------------------------------------------------
# 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 ) ->
@types.validate.dba_open_cfg ( cfg = { @types.defaults.dba_open_cfg..., cfg..., } )
{ path, schema, ram, } = cfg
#.......................................................................................................
### TAINT troublesome logic with `path` and `saveas` ###
if path?
saveas = path
else
path = '' ### TAINT or ':memory:' depending on `cfg.disk` ###
saveas = null
#.......................................................................................................
if ram then @_open_file_db_in_ram { path, schema, saveas, }
else @_attach { path, schema, saveas, }
#.......................................................................................................
return null
#---------------------------------------------------------------------------------------------------------
save: ( cfg ) ->
### TAINT could implement prohibition of `path` in type `dba_save_cfg` ###
@types.validate.dba_save_cfg ( cfg = { @types.defaults.dba_export_cfg..., cfg..., } )
{ schema
path } = cfg
throw new E.Dba_argument_not_allowed '^dba@303^', 'path', path if path?
path = @_schemas[ schema ]?.path ? null
throw new E.Dba_schema_unknown '^dba@304^', schema unless path?
return @export { schema, path, format: 'sqlite', }
#---------------------------------------------------------------------------------------------------------
export: ( cfg ) ->
### TAINT add boolean `cfg.overwrite` ###
@types.validate.dba_export_cfg ( cfg = { @types.defaults.dba_export_cfg..., cfg..., } )
{ schema
path
format } = cfg
format ?= @_format_from_path path
throw new E.Dba_extension_unknown '^dba@305^', path unless format?
switch format
when 'sqlite' then @_vacuum_atomically { schema, path, }
### TAINT when format derived from path, may be undefined, making the error message unintelligible ###
else throw new E.Dba_format_unknown '^dba@306^', format
return null
#---------------------------------------------------------------------------------------------------------
_vacuum_atomically: ( cfg ) ->
@types.validate.dba_vacuum_atomically ( cfg = { @types.defaults.dba_vacuum_atomically..., cfg..., } )
{ schema
path } = cfg
schema_i = @sql.I schema
try
tmpdir_path = TMP.directory { prefix: @cfg._temp_prefix, }
tmpfile_path = PATH.join tmpdir_path, PATH.basename path
@run "vacuum #{schema_i} into ?;", [ tmpfile_path, ]
FS.renameSync tmpfile_path, path
finally
FS.rmdirSync tmpdir_path
return null
#---------------------------------------------------------------------------------------------------------
is_ram_db: ( cfg ) ->
@types.validate.dba_is_ram_db_cfg ( cfg = { @types.defaults.dba_is_ram_db_cfg..., cfg..., } )
{ schema } = cfg
sql = "select file from pragma_database_list where name = ? limit 1;"
try
return @types.isa.dba_ram_path @single_value @query sql, [ schema, ]
catch error
throw new E.Dba_schema_unknown '^dba@307^', schema if error instanceof E.Dba_expected_one_row
throw error
#---------------------------------------------------------------------------------------------------------
_list_temp_schema_numbers: ->
matcher = @cfg._temp_prefix + '%'
sql = """
select
cast( substring( name, ? ) as integer ) as n
from pragma_database_list
where name like ?;"""
return @all_first_values @query sql, [ @cfg._temp_prefix.length + 1, matcher, ]
#---------------------------------------------------------------------------------------------------------
_max_temp_schema_number: ->
matcher = @cfg._temp_prefix + '%'
sql = """
select
max( cast( substring( name, ? ) as integer ) ) as n
from pragma_database_list
where name like ?;"""
return ( @first_value @query sql, [ @cfg._temp_prefix.length + 1, matcher, ] ) ? 0
#---------------------------------------------------------------------------------------------------------
_get_free_temp_schema: -> @cfg._temp_prefix + "#{( @_max_temp_schema_number() + 1 )}"
#=========================================================================================================
# DEBUGGING
#---------------------------------------------------------------------------------------------------------
_echo: ( ref, sql ) ->
return null unless @_dbg.echo
echo ( CND.reverse CND.blue "^icql@888-#{ref}^" ) + ( CND.reverse CND.yellow sql )
return null
#---------------------------------------------------------------------------------------------------------
_debug: ( P... ) ->
return null unless @_dbg.debug
debug P...
return null
#=========================================================================================================
# QUERY RESULT ADAPTERS
#---------------------------------------------------------------------------------------------------------
limit: ( n, iterator ) ->
count = 0
for x from iterator
return if count >= n
count += +1
yield x
return
#---------------------------------------------------------------------------------------------------------
single_row: ( iterator ) ->
throw new E.Dba_expected_one_row 'dba@763^', 0 if ( R = @first_row iterator ) is undefined
return R
#---------------------------------------------------------------------------------------------------------
all_first_values: ( iterator ) ->
R = []
for row from iterator
for key, value of row
R.push value
break
return R
#---------------------------------------------------------------------------------------------------------
first_values: ( iterator ) ->
R = []
for row from iterator
for key, value of row
yield value
return R
#---------------------------------------------------------------------------------------------------------
first_row: ( iterator ) -> return row for row from iterator
### TAINT must ensure order of keys in row is same as order of fields in query ###
single_value: ( iterator ) -> return value for key, value of @single_row iterator
first_value: ( iterator ) -> return value for key, value of @first_row iterator
list: ( iterator ) -> [ iterator..., ]
#=========================================================================================================
# QUERYING
#---------------------------------------------------------------------------------------------------------
query: ( sql, P... ) ->
@_echo 'query', sql
statement = ( @_statements[ sql ] ?= @sqlt.prepare sql )
return statement.iterate P...
#---------------------------------------------------------------------------------------------------------
run: ( sql, P... ) ->
@_echo 'run', sql
statement = ( @_statements[ sql ] ?= @sqlt.prepare sql )
return statement.run P...
#---------------------------------------------------------------------------------------------------------
_run_or_query: ( entry_type, is_last, sql, Q ) ->
@_echo '_run_or_query', sql
statement = ( @_statements[ sql ] ?= @sqlt.prepare sql )
returns_data = statement.reader
#.......................................................................................................
### Always use `run()` method if statement does not return data: ###
unless returns_data
return if Q? then ( statement.run Q ) else statement.run()
#.......................................................................................................
### If statement does return data, consume iterator unless this is the last statement: ###
if ( entry_type is 'procedure' ) or ( not is_last )
return if Q? then ( statement.all Q ) else statement.all()
#.......................................................................................................
### Return iterator: ###
return if Q? then ( statement.iterate Q ) else statement.iterate()
#---------------------------------------------------------------------------------------------------------
execute: ( sql ) ->
throw new E.Dba_argument_not_allowed '^dba@308^', "extra", rpr x if ( x = arguments[ 1 ] )?
@_echo 'execute', sql
return @sqlt.exec sql
#---------------------------------------------------------------------------------------------------------
prepare: ( sql ) ->
@_echo 'prepare', sql
return @sqlt.prepare sql
#=========================================================================================================
# OTHER
#---------------------------------------------------------------------------------------------------------
backup: ( P... ) -> @sqlt.backup P...
checkpoint: ( P... ) -> @sqlt.checkpoint P...
close: ( P... ) -> @sqlt.close P...
read: ( path ) -> @sqlt.exec FS.readFileSync path, { encoding: 'utf-8', }
load_extension: ( P... ) -> @sqlt.loadExtension P...
pragma: ( P... ) -> @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()"
@query "select * from sqlite_schema order by type desc, name;"
#---------------------------------------------------------------------------------------------------------
walk_objects: ( cfg ) ->
@types.validate.dba_walk_objects_cfg ( cfg = { @types.defaults.dba_walk_objects_cfg..., cfg..., } )
schema = cfg.schema
ordering = cfg._ordering
return @_walk_all_objects() unless schema?
schema_i = @sql.I schema
ordering_x = if ( ordering is 'drop' ) then 'desc' else 'asc'
seq = @first_value @query \
@sql.SQL"select seq from pragma_database_list where name = #{@sql.L schema};"
#.......................................................................................................
return @query @sql.SQL"""
select
#{seq} as seq,
#{@sql.L schema} as schema,
name as name,
type as type,
sql as sql
from #{@sql.I schema}.sqlite_schema
order by seq, type #{ordering_x}, name;"""
#---------------------------------------------------------------------------------------------------------
_walk_all_objects: ->
schemas = {}
parts = []
#.......................................................................................................
### TAINT use API ###
for row from @query SQL"select seq, name, file as path from pragma_database_list order by seq;"
schemas[ row.name ] = row
#.......................................................................................................
for schema, d of schemas
schema_i = @sql.I schema
schema_l = @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 @query sql
#---------------------------------------------------------------------------------------------------------
is_empty: ( cfg ) ->
@types.validate.dba_is_empty_cfg ( cfg = { @types.defaults.dba_is_empty_cfg..., cfg..., } )
return ( has_schema = @_is_empty_schema @sql.I cfg.schema ) unless name?
throw new E.Dba_not_implemented '^dba@310^', "dba.is_empty() for anything but schemas (got #{rpr cfg})"
#---------------------------------------------------------------------------------------------------------
_is_empty_schema: ( schema_i ) -> (
@list @query "select 1 from #{schema_i}.sqlite_schema limit 1;" ).length is 0
#---------------------------------------------------------------------------------------------------------
list_schemas: -> @list @query "select * from pragma_database_list order by name;"
list_schema_names: -> ( d.name for d in @list_schemas() )
#---------------------------------------------------------------------------------------------------------
has: ( cfg ) ->
@types.validate.dba_has_cfg ( cfg = { @types.defaults.dba_has_cfg..., cfg..., } )
return cfg.schema in @list_schema_names()
#---------------------------------------------------------------------------------------------------------
get_schemas: ->
R = {}
R[ row.name ] = row.file for row from @query "select * from pragma_database_list order by seq;"
return R
#---------------------------------------------------------------------------------------------------------
_path_of_schema: ( schema, fallback = misfit ) ->
R = @first_value @query "select file from pragma_database_list where name = ?;", [ schema, ]
return R if R?
return fallback unless fallback is misfit
throw new E.Dba_schema_unknown '^dba@311^', schema
#---------------------------------------------------------------------------------------------------------
type_of: ( cfg ) ->
@types.validate.dba_type_of_cfg ( cfg = { @types.defaults.dba_type_of_cfg..., cfg..., } )
{ name, schema, } = cfg
if name in [ 'sqlite_schema', 'sqlite_master', ]
return 'table'
row = @first_row @query SQL"""
select type from #{@sql.I schema}.sqlite_schema
where name = $name
limit 1;""", { name, }
throw new E.Dba_object_unknown '^dba@311^', schema, name unless row?
return row.type
#---------------------------------------------------------------------------------------------------------
fields_of: ( cfg ) ->
@types.validate.dba_fields_of_cfg ( cfg = { @types.defaults.dba_fields_of_cfg..., cfg..., } )
{ name, schema, } = cfg
schema_i = @sql.I schema
R = {}
for d from @query SQL"select * from #{schema_i}.pragma_table_info( $name );", { name, }
# { cid: 0, name: 'id', type: 'integer', notnull: 1, dflt_value: null, pk: 1 }
type = if d.type is '' then null else 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 ) ->
# try
@types.validate.dba_field_names_of_cfg ( cfg = { @types.defaults.dba_field_names_of_cfg..., cfg..., } )
{ name, schema, } = cfg
schema_i = @sql.I schema
return ( d.name for d from @query SQL"select name from #{schema_i}.pragma_table_info( $name );", { name, } )
# catch error
# throw new E.Dba_sqlite_error '^dba@111^', error
#---------------------------------------------------------------------------------------------------------
dump_relation: ( cfg ) ->
@types.validate.dba_dump_relation_cfg ( cfg = { @types.defaults.dba_dump_relation_cfg..., cfg..., } )
{ schema
name
order_by
limit } = cfg
schema_i = @sql.I schema
qname_i = schema_i + '.' + @sql.I name
limit = if cfg.limit is null then 1e9 else cfg.limit
order_by ?= 'random()'
return @query SQL"select * from #{qname_i} order by #{order_by} limit #{limit};"
#---------------------------------------------------------------------------------------------------------
_dependencies_of: ( table, schema = 'main' ) ->
return @query "pragma #{@sql.I schema}.foreign_key_list( #{@sql.I table} )"
#---------------------------------------------------------------------------------------------------------
dependencies_of: ( table, schema = 'main' ) ->
@types.validate.ic_schema schema
return ( row.table for row from @_dependencies_of table )
#=========================================================================================================
# DB STRUCTURE MODIFICATION
#---------------------------------------------------------------------------------------------------------
### TAINT Error: index associated with UNIQUE or PRIMARY KEY constraint cannot be dropped ###
clear: ( cfg ) ->
@types.validate.dba_clear_cfg ( cfg = { @types.defaults.dba_clear_cfg..., cfg..., } )
{ schema, } = cfg
schema_i = @sql.I schema
R = 0
fk_state = @get_foreign_keys_state()
@set_foreign_keys_state off
for { type, name, } in @list @walk_objects { schema, _ordering: 'drop', }
statement = "drop #{type} if exists #{@sql.I name};"
@execute statement
R += +1
@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? ###
schema_main_allowed = not @_state.initialized
{ path, schema, saveas, } = cfg
return @_attach { schema, path, saveas, } if @types.isa.dba_ram_path path
#.......................................................................................................
tmp_schema = @_get_free_temp_schema()
@_attach { schema: tmp_schema, path, }
unless ( schema is 'main' ) and schema_main_allowed
@_attach { schema, path: '', saveas, }
@_copy_schema { from_schema: tmp_schema, to_schema: schema, }
@_detach { schema: tmp_schema, }
#.......................................................................................................
@_schemas = lets @_schemas, ( d ) => d[ schema ] = { path: saveas, } ### TAINT use API call ###
return null
#---------------------------------------------------------------------------------------------------------
_attach: ( cfg ) ->
### 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.
###
@types.validate.dba_attach_cfg ( cfg = { @types.defaults.dba_attach_cfg..., cfg..., } )
{ path, schema, saveas, } = cfg
#.......................................................................................................
unless @_state.initialized
if schema is 'main'
### TAINT code duplication from oneoff handler ###
connection = new_bsqlt3_connection path, @_bsqlt3_cfg
@initialize_sqlt connection
guy.props.def @, 'sqlt', enumerable: false, configurable: false, value: connection
@_state.initialized = true
@_schemas = lets @_schemas, ( d ) =>
d[ schema ] = { path: saveas, } ### TAINT use API call ###
return null
ignore = @sqlt ### NOTE retrieve dynamic attribute for side effect, ignore its value ###
#.......................................................................................................
if @has { schema, }
throw new E.Dba_schema_exists '^dba@312^', schema
#.......................................................................................................
try
@run "attach ? as ?;", [ path, schema, ]
catch error
throw error unless error.code is 'SQLITE_ERROR'
throw new E.Dba_sqlite_too_many_dbs '^dba@313^', schema if error.message.startsWith 'too many attached databases'
throw new E.Dba_sqlite_error '^dba@314^', error
@_schemas = lets @_schemas, ( d ) => d[ schema ] = { path: saveas, } ### TAINT use API call ###
return null
#---------------------------------------------------------------------------------------------------------
_detach: ( cfg ) ->
@types.validate.dba_detach_cfg ( cfg = { @types.defaults.dba_detach_cfg..., cfg..., } )
@execute @sql.SQL"detach #{@sql.I cfg.schema};"
@_schemas = lets @_schemas, ( d ) => delete d[ cfg.schema ]
return null
#=========================================================================================================
# IN-MEMORY PROCESSING
#-----------------------------------------------------------------------------------------------------------
_move_schema: ( cfg ) -> @_copy_or_move_schema cfg, true
_copy_schema: ( cfg ) -> @_copy_or_move_schema cfg, false
#-----------------------------------------------------------------------------------------------------------
_copy_or_move_schema: ( cfg, detach_schema = false ) ->
detach_from_schema = ->
return null unless detach_schema
return @_detach { schema: from_schema, }
#.......................................................................................................
@types.validate.copy_or_move_schema_cfg ( cfg = { @types.defaults.copy_or_move_schema_cfg..., cfg..., } )
{ from_schema, to_schema, } = cfg
#.......................................................................................................
if from_schema is to_schema
throw new E.Dba_schema_repeated '^dba@315^', from_schema
#.......................................................................................................
known_schemas = @list_schema_names()
throw new E.Dba_schema_unknown '^dba@316^', from_schema unless from_schema in known_schemas
throw new E.Dba_schema_unknown '^dba@317^', to_schema unless to_schema in known_schemas
#.......................................................................................................
to_schema_objects = @list @walk_objects { schema: to_schema, }
if to_schema_objects.length > 0
throw new E.Dba_schema_nonempty '^dba@318^', to_schema
#.......................................................................................................
from_schema_objects = @list @walk_objects { schema: from_schema }
return detach_from_schema() if from_schema_objects.length is 0
#.......................................................................................................
to_schema_x = @sql.I to_schema
from_schema_x = @sql.I from_schema
inserts = []
fk_state = @get_foreign_keys_state()
@set_foreign_keys_state off
#.......................................................................................................
for d in from_schema_objects
continue if ( not d.sql? ) or ( d.sql is '' )
continue if d.name in [ 'sqlite_sequence', ]
#.....................................................................................................
### TAINT consider to use `validate.ic_db_object_type` ###
unless d.type in [ 'table', 'view', '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 = @sql.I d.name
sql = d.sql.replace /\s*CREATE\s*(TABLE|INDEX|VIEW)\s*/i, "create #{d.type} #{to_schema_x}."
#.....................................................................................................
if sql is d.sql
throw new E.Dba_unexpected_sql '^dba@320^', d.sql
#.....................................................................................................
@execute sql
if d.type is 'table'
inserts.push "insert into #{to_schema_x}.#{name_x} select * from #{from_schema_x}.#{name_x};"
#.......................................................................................................
@execute sql for sql in inserts
@set_foreign_keys_state fk_state
@pragma "#{@sql.I to_schema}.foreign_key_check;" if fk_state
return detach_from_schema()
#=========================================================================================================
# 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`.###
@_rnd_int_cfg: null
_initialize_prng: ->
clasz = @constructor
if clasz._rnd_int_cfg?
seed = clasz._rnd_int_cfg.seed ? 12.34
delta = clasz._rnd_int_cfg.delta ? 1
@_rnd_int = CND.get_rnd_int seed, delta
else
@_rnd_int = CND.random_integer.bind CND
return null
#---------------------------------------------------------------------------------------------------------
_get_connection_url: ( dbnick = null ) =>
### 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. ###
dbnick ?= "_icql_#{@_rnd_int 1_000_000_000, 9_999_999_999}"
url = "file:#{dbnick}?mode=memory&cache=shared"
return { url, dbnick, }