DBD-D1
view release on metacpan or search on metacpan
lib/DBD/D1.pm view on Meta::CPAN
# Pass Cloudflare API token as $password.
sub connect {
my ($drh, $dsn, $user, $auth, $attr) = @_;
my %dsnargs;
for my $pair (split /;/, $dsn) {
my ($k, $v) = split /=/, $pair, 2;
$dsnargs{$k} = $v if defined $k && defined $v;
}
# Use DBI->set_err on the drh with the caller's err/errstr so that
# PrintError/RaiseError on the caller handle control output, not the drh.
my $account_id = $dsnargs{account_id}
or return $drh->set_err(1,
"DBD::D1 connect: 'account_id' missing from DSN", undef, 'connect');
my $database_id = $dsnargs{database_id}
or return $drh->set_err(1,
"DBD::D1 connect: 'database_id' missing from DSN", undef, 'connect');
my $api_token = $auth || $dsnargs{api_token}
or return $drh->set_err(1,
"DBD::D1 connect: Cloudflare API token required (pass as password)", undef, 'connect');
my ($outer, $dbh) = DBI::_new_dbh($drh, { Name => $dsn });
$dbh->{Active} = 1;
$dbh->{d1_account_id} = $account_id;
$dbh->{d1_database_id} = $database_id;
$dbh->{d1_api_token} = $api_token;
return $outer;
}
sub data_sources { () }
sub disconnect_all { }
# ---------------------------------------------------------------
# DBD::D1::db â database handle
# ---------------------------------------------------------------
package DBD::D1::db;
use strict;
use warnings;
$DBD::D1::db::imp_data_size = 0;
sub prepare {
my ($dbh, $statement, @attribs) = @_;
my ($outer, $sth) = DBI::_new_sth($dbh, { Statement => $statement });
# Count ? placeholders outside quoted strings
(my $copy = $statement) =~ s/'[^']*'|"[^"]*"//g;
my $num_params = () = $copy =~ /\?/g;
$sth->{NUM_OF_PARAMS} = $num_params;
$sth->{d1_params} = [];
$sth->{d1_rows_affected} = undef;
$sth->{d1_result_data} = undef;
$sth->{d1_cursor} = 0;
return $outer;
}
sub commit {
my ($dbh) = @_;
warn "DBD::D1: commit() has no effect â D1 is AutoCommit only\n"
if $dbh->{Warn};
return 1;
}
sub rollback {
my ($dbh) = @_;
warn "DBD::D1: rollback() has no effect â D1 is AutoCommit only\n"
if $dbh->{Warn};
return 0;
}
sub disconnect {
my ($dbh) = @_;
$dbh->{Active} = 0;
return 1;
}
sub ping {
my ($dbh) = @_;
my $prev_raise = $dbh->{RaiseError};
my $prev_print = $dbh->{PrintError};
$dbh->{RaiseError} = 0;
$dbh->{PrintError} = 0;
my $ok = 0;
eval {
my $sth = $dbh->prepare('SELECT 1');
$ok = 1 if $sth && $sth->execute();
};
$dbh->{RaiseError} = $prev_raise;
$dbh->{PrintError} = $prev_print;
return $ok;
}
sub FETCH {
my ($dbh, $attr) = @_;
return 1 if $attr eq 'AutoCommit';
return $dbh->{$attr} if $attr =~ /^d1_/;
return $dbh->SUPER::FETCH($attr);
}
sub STORE {
my ($dbh, $attr, $val) = @_;
if ($attr eq 'AutoCommit') {
die "DBD::D1: AutoCommit cannot be disabled\n" unless $val;
return 1;
}
if ($attr =~ /^d1_/) { $dbh->{$attr} = $val; return 1 }
return $dbh->SUPER::STORE($attr, $val);
}
sub table_info {
lib/DBD/D1.pm view on Meta::CPAN
# ---------------------------------------------------------------
# DBD::D1::st â statement handle
# ---------------------------------------------------------------
package DBD::D1::st;
use strict;
use warnings;
$DBD::D1::st::imp_data_size = 0;
sub bind_param {
my ($sth, $pNum, $val, $attr) = @_;
$sth->{d1_params}[$pNum - 1] = $val;
return 1;
}
sub execute {
my ($sth, @bind_values) = @_;
my @params = @bind_values ? @bind_values : @{ $sth->{d1_params} // [] };
my $dbh = $sth->{Database};
my $account_id = $dbh->{d1_account_id};
my $database_id = $dbh->{d1_database_id};
my $api_token = $dbh->{d1_api_token};
my $sql = $sth->{Statement};
my ($result, $err) = DBD::D1::_http::query(
$account_id, $database_id, $api_token, $sql, \@params,
);
if (defined $err) {
return $sth->set_err(1, $err);
}
# D1 REST returns an array of result objects (one per statement).
my $res = ref($result) eq 'ARRAY' ? $result->[0] : $result;
my $rows = $res->{results} // []; # array of hashrefs
my $meta = $res->{meta} // {};
if (@$rows) {
my @col_names = sort keys %{ $rows->[0] };
# Must use direct hash assignment â STORE() rejects DBI read-only attrs
$sth->{NAME} = \@col_names;
$sth->{NAME_lc} = [ map { lc $_ } @col_names ];
$sth->{NAME_uc} = [ map { uc $_ } @col_names ];
$sth->{NUM_OF_FIELDS} = scalar @col_names;
$sth->{d1_result_data} = [
map { my $r = $_; [ @{$r}{@col_names} ] } @$rows
];
} else {
$sth->{NAME} = [];
$sth->{NAME_lc} = [];
$sth->{NAME_uc} = [];
$sth->{NUM_OF_FIELDS} = 0;
$sth->{d1_result_data} = [];
}
$sth->{d1_cursor} = 0;
$sth->{d1_rows_affected} = $meta->{changes} // $meta->{rows_affected} // 0;
$sth->{Active} = 1;
return $sth->{d1_rows_affected} || '0E0';
}
sub fetchrow_arrayref {
my ($sth) = @_;
my $data = $sth->{d1_result_data} or return undef;
my $cursor = $sth->{d1_cursor};
if ($cursor >= scalar @$data) {
$sth->{Active} = 0;
return undef;
}
$sth->{d1_cursor}++;
return $data->[$cursor];
}
*fetch = \&fetchrow_arrayref;
sub fetchall_arrayref {
my ($sth, $slice, $max_rows) = @_;
my $data = $sth->{d1_result_data} // [];
my @result;
for my $row (@$data) {
last if defined $max_rows && @result >= $max_rows;
if (!defined $slice) {
push @result, [@$row];
} elsif (ref $slice eq 'HASH') {
my $names = $sth->{NAME} // [];
my %h; @h{@$names} = @$row;
my @keys = keys %$slice ? keys %$slice : keys %h;
push @result, { map { $_ => $h{$_} } @keys };
} elsif (ref $slice eq 'ARRAY') {
push @result, [ @{$row}[@$slice] ];
}
}
$sth->{Active} = 0;
return \@result;
}
sub rows { $_[0]->{d1_rows_affected} // -1 }
sub finish {
my ($sth) = @_;
$sth->{Active} = 0;
$sth->{d1_result_data} = undef;
$sth->{d1_cursor} = 0;
return 1;
}
sub FETCH {
my ($sth, $attr) = @_;
return $sth->{$attr} if $attr =~ /^d1_/;
return $sth->SUPER::FETCH($attr);
}
sub STORE {
my ($sth, $attr, $val) = @_;
if ($attr =~ /^d1_/) { $sth->{$attr} = $val; return 1 }
return $sth->SUPER::STORE($attr, $val);
}
1;
__END__
=pod
=encoding UTF-8
=head1 NAME
DBD::D1 - DBI driver for Cloudflare D1 (serverless SQLite)
=head1 VERSION
0.02
=head1 SYNOPSIS
use DBI;
my $dbh = DBI->connect(
'dbi:D1:account_id=<ACCOUNT_ID>;database_id=<DATABASE_ID>',
undef,
$ENV{CF_API_TOKEN},
{ RaiseError => 1, PrintError => 0 },
) or die $DBI::errstr;
my $sth = $dbh->prepare('SELECT * FROM users WHERE active = ?');
$sth->execute(1);
while (my $row = $sth->fetchrow_hashref) {
printf "%s <%s>\n", $row->{name}, $row->{email};
}
$dbh->disconnect;
=head1 DESCRIPTION
B<DBD::D1> is a L<DBI> driver for L<Cloudflare D1|https://developers.cloudflare.com/d1/>,
Cloudflare's serverless SQLite-compatible relational database.
It communicates via the D1 REST API using L<HTTP::Tiny> and L<JSON::PP>
(both ship with Perl 5.14+), so no compiled extensions are required.
=head1 DSN FORMAT
( run in 2.944 seconds using v1.01-cache-2.11-cpan-cdf2f3d4e48 )