BusyBird
view release on metacpan or search on metacpan
lib/BusyBird/StatusStorage/SQLite.pm view on Meta::CPAN
use JSON;
use Scalar::Util qw(looks_like_number);
use DateTime::Format::Strptime;
use DateTime;
no autovivification;
my @STATUSES_ORDER_BY = ('utc_acked_at DESC', 'utc_created_at DESC', 'status_id DESC');
my $DELETE_COUNT_ID = 0;
my $UNDEF_TIMESTAMP = '9999-99-99T99:99:99';
{
my $TIMESTAMP_FORMAT_STR = '%Y-%m-%dT%H:%M:%S';
my $TIMESTAMP_FORMAT = DateTime::Format::Strptime->new(
pattern => $TIMESTAMP_FORMAT_STR,
time_zone => 'UTC',
on_error => 'croak',
);
sub _format_datetime {
my ($dt) = @_;
return $dt->strftime($TIMESTAMP_FORMAT_STR);
}
sub _parse_datetime {
my ($dt_str) = @_;
return $TIMESTAMP_FORMAT->parse_datetime($dt_str);
}
}
sub new {
my ($class, %args) = @_;
my $self = bless {
maker => SQL::Maker->new(driver => 'SQLite', strict => 1),
in_memory_dbh => undef,
}, $class;
$self->set_param(\%args, "path", undef, 1);
$self->set_param(\%args, "max_status_num", 2000);
$self->set_param(\%args, "hard_max_status_num", int($self->{max_status_num} * 1.2));
$self->set_param(\%args, "vacuum_on_delete", int($self->{max_status_num} * 2.0));
croak "max_status_num must be a number" if !looks_like_number($self->{max_status_num});
croak "hard_max_status_num must be a number" if !looks_like_number($self->{hard_max_status_num});
$self->{max_status_num} = int($self->{max_status_num});
$self->{hard_max_status_num} = int($self->{hard_max_status_num});
croak "hard_max_status_num must be >= max_status_num" if !($self->{hard_max_status_num} >= $self->{max_status_num});
$self->_create_tables();
return $self;
}
sub _create_new_dbh {
my ($self, @connect_params) = @_;
my $dbh = DBI->connect(@connect_params);
$dbh->do(q{PRAGMA foreign_keys = ON});
return $dbh;
}
sub _get_my_dbh {
my ($self) = @_;
my @connect_params = ("dbi:SQLite:dbname=$self->{path}", "", "", {
RaiseError => 1, PrintError => 0, AutoCommit => 1, sqlite_unicode => 1,
});
if($self->{path} eq ':memory:') {
$self->{in_memory_dbh} = $self->_create_new_dbh(@connect_params) if !$self->{in_memory_dbh};
return $self->{in_memory_dbh};
}
return $self->_create_new_dbh(@connect_params);
}
sub _create_tables {
my ($self) = @_;
my $dbh = $self->_get_my_dbh();
$dbh->do(<<EOD);
CREATE TABLE IF NOT EXISTS timelines (
timeline_id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
name TEXT UNIQUE NOT NULL
)
EOD
$dbh->do(<<EOD);
CREATE TABLE IF NOT EXISTS statuses (
timeline_id INTEGER NOT NULL
REFERENCES timelines(timeline_id) ON DELETE CASCADE ON UPDATE CASCADE,
status_id TEXT NOT NULL,
utc_acked_at TEXT NOT NULL,
utc_created_at TEXT NOT NULL,
timezone_acked_at TEXT NOT NULL,
timezone_created_at TEXT NOT NULL,
level INTEGER NOT NULL,
content TEXT NOT NULL,
PRIMARY KEY (timeline_id, status_id)
)
EOD
$dbh->do(<<EOD);
CREATE TABLE IF NOT EXISTS delete_counts (
delete_count_id INTEGER PRIMARY KEY NOT NULL,
delete_count INTEGER NOT NULL
)
EOD
my ($sql, @bind) = $self->{maker}->insert('delete_counts', [
delete_count_id => $DELETE_COUNT_ID, delete_count => 0
], {prefix => 'INSERT OR IGNORE INTO'});
$dbh->do($sql, undef, @bind);
}
sub _record_hash_to_array {
my ($record) = @_;
return [ map { $_, $record->{$_} } sort { $a cmp $b } keys %$record ];
}
sub _put_update {
my ($self, $dbh, $record, $prev_sth) = @_;
my $sth = $prev_sth;
my ($sql, @bind) = $self->{maker}->update('statuses', _record_hash_to_array($record), sql_and([
sql_eq('timeline_id' => $record->{timeline_id}), sql_eq(status_id => $record->{status_id})
]));
if(!$sth) {
## Or, should we check $sql is not changed...?
$sth = $dbh->prepare($sql);
}
return ($sth->execute(@bind), $sth);
( run in 2.978 seconds using v1.01-cache-2.11-cpan-39bf76dae61 )