@@ -0,0 +1,1058 @@
+# <@LICENSE>
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to you under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at:
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+# </@LICENSE>
+
+=head1 NAME
+
+Mail::SpamAssassin::BayesStore::PgSQL - PostgreSQL Specific Bayesian Storage Module Implementation
+
+=head1 SYNOPSIS
+
+=head1 DESCRIPTION
+
+This module implementes a PostgresSQL specific bayesian storage module.
+
+It subclasses Mail::SpamAssassin::BayesStore::SQL and overrides any methods
+which makes SQL calls involving the token column. Since PostgreSQL uses BYTEA
+for the token column type you must make sure that the DBD driver does the proper
+quoting. You can accomplish this by binding the token column to a specific type.
+
+=cut
+
+package Mail::SpamAssassin::BayesStore::PgSQL;
+
+use strict;
+use warnings;
+use bytes;
+
+use Mail::SpamAssassin::BayesStore::SQL;
+use Mail::SpamAssassin::Logger;
+
+use vars qw( @ISA );
+
+@ISA = qw( Mail::SpamAssassin::BayesStore::SQL );
+
+use constant HAS_DBI => eval { require DBI; };
+
+# We need this so we can import the pg_types, since this is a DBD::Pg specific module it should be ok
+# YUCK! This little require/import trick is required for the rpm stuff
+BEGIN { require DBD::Pg; import DBD::Pg qw(:pg_types); }
+
+=head1 METHODS
+
+=head2 token_expiration
+
+public instance (Integer, Integer,
+ Integer, Integer) token_expiration(\% $opts,
+ Integer $newdelta,
+ @ @vars)
+
+Description:
+This method performs the database specific expiration of tokens based on
+the passed in C<$newdelta> and C<@vars>.
+
+=cut
+
+sub token_expiration {
+ my ($self, $opts, $newdelta, @vars) = @_;
+
+ my $num_hapaxes;
+ my $num_lowfreq;
+ my $deleted;
+
+ # Figure out how old is too old...
+ my $too_old = $vars[10] - $newdelta; # tooold = newest - delta
+
+ # if token atime > newest, reset to newest ...
+ my $sql = "UPDATE bayes_token SET atime = ?
+ WHERE id = ?
+ AND atime > ?";
+
+ my $rows = $self->{_dbh}->do($sql, undef, $vars[10], $self->{_userid}, $vars[10]);
+
+ unless (defined($rows)) {
+ dbg("bayes: token_expiration: SQL error: ".$self->{_dbh}->errstr());
+ $deleted = 0;
+ $self->{_dbh}->rollback();
+ goto token_expiration_final;
+ }
+
+ # Check to make sure the expire won't remove too many tokens
+ $sql = "SELECT count(token) FROM bayes_token
+ WHERE id = ?
+ AND atime < ?";
+
+ my $sth = $self->{_dbh}->prepare_cached($sql);
+
+ unless (defined($sth)) {
+ dbg("bayes: token_expiration: SQL error: ".$self->{_dbh}->errstr());
+ $deleted = 0;
+ $self->{_dbh}->rollback();
+ goto token_expiration_final;
+ }
+
+ my $rc = $sth->execute($self->{_userid}, $too_old);
+
+ unless ($rc) {
+ dbg("bayes: token_expiration: SQL error: ".$self->{_dbh}->errstr());
+ $deleted = 0;
+ $self->{_dbh}->rollback();
+ goto token_expiration_final;
+ }
+
+ my ($count) = $sth->fetchrow_array();
+
+ $sth->finish();
+
+ # Sanity check: if we expired too many tokens, abort!
+ if ($vars[3] - $count < 100000) {
+ dbg("bayes: token expiration would expire too many tokens, aborting");
+ # set these appropriately so the next expire pass does the first pass
+ $deleted = 0;
+ $newdelta = 0;
+ }
+ else {
+ # Do the expire
+ $sql = "DELETE from bayes_token
+ WHERE id = ?
+ AND atime < ?";
+
+ $rows = $self->{_dbh}->do($sql, undef, $self->{_userid}, $too_old);
+
+ unless (defined($rows)) {
+ dbg("bayes: token_expiration: SQL error: ".$self->{_dbh}->errstr());
+ $deleted = 0;
+ $self->{_dbh}->rollback();
+ goto token_expiration_final;
+ }
+
+ $deleted = $rows;
+ }
+
+ # Update the magic tokens as appropriate
+ $sql = "UPDATE bayes_vars SET token_count = token_count - ?,
+ last_expire = ?,
+ last_atime_delta = ?,
+ last_expire_reduce = ?,
+ oldest_token_age = (SELECT min(atime)
+ FROM bayes_token
+ WHERE id = ?)
+ WHERE id = ?";
+
+ $rows = $self->{_dbh}->do($sql, undef, $deleted, time(), $newdelta, $deleted, $self->{_userid}, $self->{_userid});
+
+ unless (defined($rows)) {
+ # Very bad, we actually deleted the tokens, but were unable to update
+ # bayes_vars with the new data.
+ dbg("bayes: token_expiration: SQL error: ".$self->{_dbh}->errstr());
+ $self->{_dbh}->rollback();
+ $deleted = 0;
+ goto token_expiration_final;
+ }
+
+ $self->{_dbh}->commit();
+
+token_expiration_final:
+ my $kept = $vars[3] - $deleted;
+
+ $num_hapaxes = $self->_get_num_hapaxes() if ($opts->{verbose});
+ $num_lowfreq = $self->_get_num_lowfreq() if ($opts->{verbose});
+
+ # Call untie_db() first so we unlock correctly etc. first
+ $self->untie_db();
+
+ return ($kept, $deleted, $num_hapaxes, $num_lowfreq);
+}
+
+=head2 seen_put
+
+public (Boolean) seen_put (string $msgid, char $flag)
+
+Description:
+This method records C<$msgid> as the type given by C<$flag>. C<$flag> is one of
+two values 's' for spam and 'h' for ham.
+
+=cut
+
+sub seen_put {
+ my ($self, $msgid, $flag) = @_;
+
+ return 0 if (!$msgid);
+ return 0 if (!$flag);
+
+ return 0 unless (defined($self->{_dbh}));
+
+ my $sql = "INSERT INTO bayes_seen (id, msgid, flag)
+ VALUES (?,?,?)";
|
@@ -0,0 +1,20 @@
+--- lib/Mail/SpamAssassin/BayesStore/PgSQL.pm.orig 2008-07-09 22:07:59.000000000 +0200
++++ lib/Mail/SpamAssassin/BayesStore/PgSQL.pm 2008-07-09 22:08:06.000000000 +0200
+@@ -939,7 +939,7 @@
+ }
+
+ my $escaped_token = _quote_bytea($token);
+- my $sth = $self->{_dbh}->prepare("select put_tokens($self->{_userid},$self->{_esc_prefix}'{$escaped_token}',
++ my $sth = $self->{_dbh}->prepare("select put_tokens($self->{_userid},$self->{_esc_prefix}'{$escaped_token}'::bytea[],
+ $spam_count,$ham_count,$atime)");
+
+ unless (defined($sth)) {
+@@ -1003,7 +1003,7 @@
+
+ my $tokenarray = join(",", map { '"' . _quote_bytea($_) . '"' } sort keys %{$tokens});
+
+- my $sth = $self->{_dbh}->prepare("select put_tokens($self->{_userid}, $self->{_esc_prefix}'{$tokenarray}',
++ my $sth = $self->{_dbh}->prepare("select put_tokens($self->{_userid}, $self->{_esc_prefix}'{$tokenarray}'::bytea[],
+ $spam_count, $ham_count, $atime)");
+
+ unless (defined($sth)) {
|