sub
get_column_type
{
my
$v
=
shift
;
return
unless
defined
$v
;
my
$ref
=
ref
$v
;
if
(
$ref
) {
return
'json text'
if
$ref
eq
'ARRAY'
or
$ref
eq
'HASH'
;
}
my
$obj
= B::svref_2object (\
$v
);
my
$flags
=
$obj
->FLAGS;
if
((
$flags
& (B::SVf_IOK | B::SVf_NOK | B::SVf_POK))
== (B::SVf_IOK | B::SVf_NOK | B::SVf_POK))
{
return
'bool'
if
(
$obj
->IV == 0 &&
$obj
->NV == 0 &&
$obj
->PV eq
''
)
or (
$obj
->IV == 1 &&
$obj
->NV == 1 &&
$obj
->PV eq
'1'
);
}
return
'text'
if
$flags
& B::SVf_POK;
return
'real'
if
$flags
& B::SVf_NOK;
return
'integer'
if
$flags
& B::SVf_IOK;
return
'text'
;
}
sub
convert
{
my
$data
=
shift
;
my
@retval
;
foreach
my
$row
(
ref
$data
eq
'ARRAY'
?
@$data
: (
$data
)) {
push
@retval
, [
map
{ [
$_
=>
$row
->{
$_
} ] }
sort
keys
%$row
];
}
return
\
@retval
;
}
sub
simplify
{
my
$text
=
shift
;
$text
=~ s/[^a-zA-Z0-9]//g;
return
$text
;
}
sub
new
{
my
$class
=
shift
;
my
$self
=
shift
|| {};
$self
->{dbname} ||=
'dumptruck.db'
;
$self
->{vars_table} ||=
'_dumptruckvars'
;
$self
->{auto_commit} = 1
unless
exists
$self
->{auto_commit};
$self
->{dbh} = DBI->
connect
(
"dbi:SQLite:$self->{dbname}"
,
""
,
""
, {
AutoCommit
=>
$self
->{auto_commit},
RaiseError
=> 1,
PrintError
=> 0 })
or
die
"Could get a database handle: $!"
;
$self
->{dbh}{sqlite_unicode} = 1;
return
bless
$self
,
$class
;
}
sub
column_names
{
my
$self
=
shift
;
my
$table_name
=
shift
||
'dumptruck'
;
$self
->execute (
sprintf
'PRAGMA table_info(%s)'
,
$self
->{dbh}->quote (
$table_name
))
}
sub
_check_or_create_vars_table
{
my
$self
=
shift
;
$self
->execute (
sprintf
'CREATE TABLE IF NOT EXISTS %s '
.
'(`key` text PRIMARY KEY, `value` blob, `type` text)'
,
$self
->{dbh}->quote (
$self
->{vars_table}));
}
sub
execute
{
my
$self
=
shift
;
my
$sql
=
shift
;
my
@params
=
@_
;
my
@retval
;
warn
"Executing statement: '$sql'"
if
$self
->{debug};
my
$sth
=
$self
->{dbh}->prepare (
$sql
);
$sth
->execute (
@params
);
return
[]
unless
$sth
->{NUM_OF_FIELDS};
while
(
my
$row
=
$sth
->fetch) {
my
$types
=
$sth
->{TYPE};
my
$names
=
$sth
->{NAME_lc};
push
@retval
, {};
foreach
(0..
$#$row
) {
my
$data
=
$row
->[
$_
];
$data
= decode_json (
$data
)
if
$types
->[
$_
] eq
'json text'
;
$retval
[
$#retval
]->{
$names
->[
$_
]} =
$data
;
}
};
return
\
@retval
;
}
sub
commit
{
my
$self
=
shift
;
$self
->{dbh}->commit;
}
sub
close
{
my
$self
=
shift
;
$self
->{dbh}->disconnect;
$self
->{dbh} =
undef
;
}
sub
create_index
{
my
$self
=
shift
;
my
$columns
=
shift
;
my
$table_name
=
shift
;
my
$if_not_exists
=
shift
;
$if_not_exists
= (not
defined
$if_not_exists
or
$if_not_exists
)
?
'IF NOT EXISTS'
:
''
;
my
$unique
= (
shift
) ?
'UNIQUE'
:
''
;
my
$index_name
=
join
'_'
, (simplify (
$table_name
),
map
{ simplify (
$_
) }
@$columns
);
$self
->execute (
sprintf
'CREATE %s INDEX %s %s ON %s (%s)'
,
$unique
,
$if_not_exists
,
$index_name
,
$self
->{dbh}->quote (
$table_name
),
join
(
','
,
map
{
$self
->{dbh}->quote (
$_
) }
@$columns
));
}
sub
_check_and_add_columns
{
my
$self
=
shift
;
my
$table_name
=
shift
;
my
$row
=
shift
;
foreach
(
@$row
) {
my
(
$k
,
$v
) =
@$_
;
eval
{
$self
->execute (
sprintf
'ALTER TABLE %s ADD COLUMN %s %s'
,
$self
->{dbh}->quote (
$table_name
),
$self
->{dbh}->quote (
$k
), get_column_type (
$v
)) };
die
if
$@ and not $@ =~ /duplicate column name/;
}
}
sub
create_table
{
my
$self
=
shift
;
my
$data
=
shift
;
my
$table_name
=
shift
or
die
'Need table name'
;
my
$error_if_exists
=
shift
;
my
$converted_data
= convert (
$data
);
die
'No data passed'
unless
$converted_data
and
$converted_data
->[0];
my
$startdata
=
$converted_data
->[0];
my
(
$k
,
$v
);
foreach
(
@$startdata
) {
(
$k
,
$v
) =
@$_
;
last
if
defined
$v
;
}
return
unless
$k
and
$v
;
my
$if_not_exists
=
'IF NOT EXISTS'
unless
$error_if_exists
;
$self
->execute (
sprintf
'CREATE TABLE %s %s (%s %s)'
,
$if_not_exists
,
$self
->{dbh}->quote (
$table_name
),
$self
->{dbh}->quote (
$k
), get_column_type (
$v
));
foreach
(
@$converted_data
) {
$self
->_check_and_add_columns (
$table_name
,
$_
);
}
}
sub
insert
{
my
$self
=
shift
;
my
$data
=
shift
;
my
$table_name
=
shift
||
'dumptruck'
;
my
$upsert
=
shift
;
my
$upserttext
= (
$upsert
?
'OR REPLACE'
:
''
);
$self
->create_table (
$data
,
$table_name
);
my
%column_types
=
map
{
lc
(
$_
->{name}) =>
$_
->{type} }
@{
$self
->column_names (
$table_name
)};
my
$converted_data
= convert (
$data
);
die
'No data passed'
unless
$converted_data
and
$converted_data
->[0];
my
@rowids
;
foreach
(
@$converted_data
) {
$self
->_check_and_add_columns (
$table_name
,
$_
);
my
(
@keys
,
@values
);
foreach
my
$cols
(
@$_
) {
my
(
$key
,
$value
) =
@$cols
;
my
$type
=
$column_types
{
lc
(
$key
)} or get_column_type (
$value
);
$value
= encode_json (
$value
)
if
$type
eq
'json text'
;
push
@keys
,
$key
;
push
@values
,
$value
;
}
if
(
@keys
) {
my
$question_marks
=
join
','
,
map
{
'?'
} 1..
@keys
;
$self
->execute (
sprintf
(
'INSERT %s INTO %s (%s) VALUES (%s)'
,
$upserttext
,
$self
->{dbh}->quote (
$table_name
),
join
(
','
,
@keys
),
$question_marks
),
@values
);
}
else
{
$self
->execute (
sprintf
'INSERT %s INTO %s DEFAULT VALUES'
,
$upserttext
,
$self
->{dbh}->quote (
$table_name
));
}
push
@rowids
,
$self
->execute (
'SELECT last_insert_rowid()'
)
->[0]{
'last_insert_rowid()'
};
}
return
(
ref
$data
eq
'HASH'
and
$data
->{
keys
}) ?
$rowids
[0] :
@rowids
;
}
sub
upsert
{
my
$self
=
shift
;
my
$data
=
shift
;
my
$table_name
=
shift
;
$self
->insert (
$data
,
$table_name
, 1);
}
sub
get_var
{
my
$self
=
shift
;
my
$k
=
shift
;
my
$data
=
$self
->execute(
sprintf
(
'SELECT * FROM %s WHERE `key` = ?'
,
$self
->{dbh}->quote (
$self
->{vars_table})),
$k
);
return
unless
$data
and
$data
->[0];
return
$data
->[0]{value};
}
sub
save_var
{
my
$self
=
shift
;
my
$k
=
shift
;
my
$v
=
shift
;
$self
->_check_or_create_vars_table;
$self
->execute(
sprintf
(
'INSERT OR REPLACE INTO %s '
.
'(`key`, `type`, `value`) VALUES (?, ?, ?)'
,
$self
->{dbh}->quote (
$self
->{vars_table})),
$k
, get_column_type (
$v
),
$v
);
}
sub
tables
{
my
$self
=
shift
;
map
{
$_
->{name} } @{
$self
->execute
(
'SELECT name FROM sqlite_master WHERE TYPE="table"'
)};
}
sub
dump
{
my
$self
=
shift
;
my
$table_name
=
shift
||
'dumptruck'
;
$self
->execute (
sprintf
'SELECT * FROM %s'
,
$self
->{dbh}->quote (
$table_name
))
}
sub
drop
{
my
$self
=
shift
;
my
$table_name
=
shift
||
'dumptruck'
;
my
$if_exists
=
shift
;
$self
->execute (
sprintf
'DROP TABLE %s %s'
,
(
$if_exists
?
'IF EXISTS'
:
''
),
$self
->{dbh}->quote (
$table_name
))
}
1;