about summary refs log tree commit homepage
diff options
context:
space:
mode:
authorEric Wong <e@80x24.org>2022-02-14 05:37:25 +0000
committerEric Wong <e@80x24.org>2022-02-14 18:43:16 +0000
commit2231c8b183be0be5d8a9738a3e417b5c3a09c7c7 (patch)
tree4589decb5859338bd731e7f647d69252db46a06f
parent80690e594710f2fb89d306f1f5faf4a57aea79c8 (diff)
downloadpublic-inbox-2231c8b183be0be5d8a9738a3e417b5c3a09c7c7.tar.gz
While we only store URLs and binary SHA-1/SHA-256 values in skv
at the moment, we may store potentially ambiguous keys/values in
the future.  It's possible to store "02" and have it treated as
`2' unless explicitly binding parameters as SQL_BLOB.  This
behavior was independent of the sqlite_unicode parameter as
evidenced by the new tests.

I only noticed this bug while hacking on another project using
DBD::SQLite, and not while hacking on public-inbox itself.
-rw-r--r--lib/PublicInbox/SharedKV.pm30
-rw-r--r--t/shared_kv.t3
2 files changed, 26 insertions, 7 deletions
diff --git a/lib/PublicInbox/SharedKV.pm b/lib/PublicInbox/SharedKV.pm
index d49a39c1..90ccf2b4 100644
--- a/lib/PublicInbox/SharedKV.pm
+++ b/lib/PublicInbox/SharedKV.pm
@@ -9,7 +9,7 @@ use strict;
 use v5.10.1;
 use parent qw(PublicInbox::Lock);
 use File::Temp qw(tempdir);
-use DBI ();
+use DBI qw(:sql_types); # SQL_BLOB
 use PublicInbox::Spawn;
 use File::Path qw(rmtree make_path);
 
@@ -59,9 +59,12 @@ sub new {
 sub set_maybe {
         my ($self, $key, $val, $lock) = @_;
         $lock //= $self->lock_for_scope_fast;
-        my $e = $self->{dbh}->prepare_cached(<<'')->execute($key, $val);
+        my $sth = $self->{dbh}->prepare_cached(<<'');
 INSERT OR IGNORE INTO kv (k,v) VALUES (?, ?)
 
+        $sth->bind_param(1, $key, SQL_BLOB);
+        $sth->bind_param(2, $val, SQL_BLOB);
+        my $e = $sth->execute;
         $e == 0 ? undef : $e;
 }
 
@@ -88,20 +91,30 @@ sub keys {
         } else {
                 @pfx = (); # [0] may've been undef
         }
-        map { $_->[0] } @{$self->dbh->selectall_arrayref($sql, undef, @pfx)};
+        my $sth = $self->dbh->prepare($sql);
+        if (@pfx) {
+                $sth->bind_param(1, $pfx[0], SQL_BLOB);
+                $sth->bind_param(2, $pfx[1]);
+        }
+        $sth->execute;
+        map { $_->[0] } @{$sth->fetchall_arrayref};
 }
 
 sub set {
         my ($self, $key, $val) = @_;
         if (defined $val) {
-                my $e = $self->{dbh}->prepare_cached(<<'')->execute($key, $val);
+                my $sth = $self->{dbh}->prepare_cached(<<'');
 INSERT OR REPLACE INTO kv (k,v) VALUES (?,?)
 
+                $sth->bind_param(1, $key, SQL_BLOB);
+                $sth->bind_param(2, $val, SQL_BLOB);
+                my $e = $sth->execute;
                 $e == 0 ? undef : $e;
         } else {
-                $self->{dbh}->prepare_cached(<<'')->execute($key);
+                my $sth = $self->{dbh}->prepare_cached(<<'');
 DELETE FROM kv WHERE k = ?
 
+                $sth->bind_param(1, $key, SQL_BLOB);
         }
 }
 
@@ -110,7 +123,8 @@ sub get {
         my $sth = $self->{dbh}->prepare_cached(<<'', undef, 1);
 SELECT v FROM kv WHERE k = ?
 
-        $sth->execute($key);
+        $sth->bind_param(1, $key, SQL_BLOB);
+        $sth->execute;
         $sth->fetchrow_array;
 }
 
@@ -121,9 +135,11 @@ sub xchg {
         if (defined $newval) {
                 set($self, $key, $newval);
         } else {
-                $self->{dbh}->prepare_cached(<<'')->execute($key);
+                my $sth = $self->{dbh}->prepare_cached(<<'');
 DELETE FROM kv WHERE k = ?
 
+                $sth->bind_param(1, $key, SQL_BLOB);
+                $sth->execute;
         }
         $oldval;
 }
diff --git a/t/shared_kv.t b/t/shared_kv.t
index 8b4f9c29..8dfd3b25 100644
--- a/t/shared_kv.t
+++ b/t/shared_kv.t
@@ -42,5 +42,8 @@ undef $skv;
 ok(!-d $skv_tmpdir, 'temporary dir gone');
 $skv = PublicInbox::SharedKV->new("$tmpdir/dir", 'base');
 ok(-e "$tmpdir/dir/base.sqlite3", 'file created');
+$skv->dbh;
+ok($skv->set_maybe('02', '2'), "`02' set");
+ok($skv->set_maybe('2', '2'), "`2' set (no match on `02')");
 
 done_testing;