Skip to content

Commit

Permalink
Item9808: regular expressions in MS SQL Server - thank you, Dmitri Go…
Browse files Browse the repository at this point in the history
…lovan - plus abstraction of boolean types for T-SQL, which doesn't have them

git-svn-id: http://svn.foswiki.org/trunk/DBIStoreContrib@17295 0b4bb1d4-4e5a-0410-9cc4-b2b747904278
  • Loading branch information
CrawfordCurrie authored and CrawfordCurrie committed Feb 21, 2014
1 parent d6eca6a commit 44438ec
Show file tree
Hide file tree
Showing 8 changed files with 244 additions and 162 deletions.
5 changes: 5 additions & 0 deletions data/System/DBIStoreContrib.txt
Expand Up @@ -135,6 +135,11 @@ thing to do is to use the ODBC driver with DBIStoreContrib and create a data
source for SQL Server in the ODBC Administrator which uses Windows authentication.
Then set an empty username and password for !DBIStoreContrib.

SQL Server does not come equipped with regular expression matching, which is
required for Foswiki, so you wil need to install a regular expression library.
The default personality module included in this module requires the user function =dbo.fn_RegExIsMatch= to stub the =.NET= !RegEx class. Instructions for building and installing this user function can be found at
http://www.codeproject.com/Articles/19502/A-T-SQL-Regular-Expression-Library-for-SQL-Server

---+++ SQLite Notes
SQLite requires the =pcre= module to be installed to support regular expression searches. The path to this module is set up in =configure=.

Expand Down
8 changes: 4 additions & 4 deletions lib/Foswiki/Contrib/DBIStoreContrib/DBIStore.pm
Expand Up @@ -117,8 +117,8 @@ sub _connect {

# Custom code to put DB's into ANSI mode and clean up error reporting
personality()->startup();
$CQ = personality()->string_quote();
$TEXT = personality()->text_type();
$CQ = personality()->{string_quote};
$TEXT = personality()->{text_type};

# Check if the DB is initialised with a quick sniff of the tables
# to see if all the ones we expect are there
Expand Down Expand Up @@ -199,7 +199,7 @@ SQL
print STDERR "Creating table for $t\n" if MONITOR;
$this->_createTableForMETA($t);
}
$this->{handle}->do('COMMIT') if personality()->requires_COMMIT();
$this->{handle}->do('COMMIT') if personality()->{requires_COMMIT};
}

# Load all existing webs and topics into the cache DB (expensive)
Expand All @@ -211,7 +211,7 @@ sub _preload {
my $web = $wit->next();
$this->_preloadWeb( $web, $session );
}
$this->{handle}->do('COMMIT') if personality()->requires_COMMIT();
$this->{handle}->do('COMMIT') if personality()->{requires_COMMIT};
}

# Preload a single web - PRIVATE
Expand Down
69 changes: 47 additions & 22 deletions lib/Foswiki/Contrib/DBIStoreContrib/HoistSQL.pm
Expand Up @@ -48,10 +48,17 @@ use constant {

VALUE => 12,
TABLE => 13,

# An integer type used where DB doesn't support a BOOLEAN type
PSEUDO_BOOL => 14,
};

our $table_name_RE = qr/^\w+$/;
our $cq;

# Pseudo-constants, from the Personality
our $CQ; # character string quote
our $TRUE;
our $TRUE_TYPE;

BEGIN {

Expand Down Expand Up @@ -230,6 +237,10 @@ sub _boolean_bop {
$lhs = _cast( $lhs, $lhs_type, STRING );
$rhs = _cast( $rhs, $rhs_type, STRING );
}
else {
$lhs = _cast( $lhs, $lhs_type, BOOLEAN );
$rhs = _cast( $rhs, $rhs_type, BOOLEAN );
}
return ( "($lhs)$opn($rhs)", BOOLEAN );
}

Expand All @@ -248,7 +259,7 @@ my %bop_map = (
# Special case
if ( $lhs eq 'NULL' ) {
if ( $rhs eq 'NULL' ) {
return ( _personality()->true(), BOOLEAN );
return ( $TRUE, $TRUE_TYPE );
}
return ( "($rhs) IS NULL", BOOLEAN );
}
Expand Down Expand Up @@ -305,7 +316,11 @@ Will die with a message if there is a diagnosable problem.
sub hoist {
my ($query) = @_;

$cq ||= _personality()->string_quote;
unless ( defined $TRUE ) {
$CQ = _personality()->{string_quote};
$TRUE = _personality()->{true_value};
$TRUE_TYPE = _personality()->{true_type};
}

print STDERR "HOISTING " . recreate($query) . "\n" if MONITOR;

Expand All @@ -326,7 +341,7 @@ sub hoist {
$h{sql} = "($h{sql})!= 0";
}
elsif ( $h{type} == STRING ) {
$h{sql} = "($h{sql})!=$cq$cq";
$h{sql} = "($h{sql})!=$CQ$CQ";
}
return $h{sql};
}
Expand Down Expand Up @@ -375,7 +390,7 @@ sub _hoist {
# Conbvert to an escaped SQL string
my $s = $node->{params}[0];
$s =~ s/\\/\\\\/g;
$result{sql} = "$cq$s$cq";
$result{sql} = "$CQ$s$CQ";
$result{type} = STRING;
}
elsif ( $node->{op} == NAME ) {
Expand Down Expand Up @@ -439,11 +454,14 @@ sub _hoist {
elsif ( $where{type} == STRING ) {

# A simple non-table expression
$where{sql} = "($where{sql})!=$cq$cq";
$where{sql} = "($where{sql})!=$CQ$CQ";
}
elsif ( $where{type} == NUMBER ) {
$where{sql} = "($where{sql})!=0";
}
elsif ( $where{type} == BOOLEAN ) {
$where{sql} = "($where{sql})!=0";
}

$result{sql} = _SELECT(
select => '*',
Expand Down Expand Up @@ -514,7 +532,7 @@ sub _hoist {
my $tname_sel = $tnames;
$tname_sel = "$tnames.$lhs{sel}" if $lhs{sel};
$lhs_where = "($topic_alias.name=$tname_sel OR "
. "($topic_alias.web||$cq.$cq||$topic_alias.name)=$tname_sel)";
. "($topic_alias.web||$CQ.$CQ||$topic_alias.name)=$tname_sel)";
}
elsif ( $lhs{is_table_name} ) {

Expand All @@ -526,7 +544,7 @@ sub _hoist {
# Not a selector or simple table name, must be a simple
# expression yielding a selector
$lhs_where = "($lhs{sql}) IN "
. "($topic_alias.name,$topic_alias.web||$cq.$cq||$topic_alias.name)";
. "($topic_alias.name,$topic_alias.web||$CQ.$CQ||$topic_alias.name)";
}

# Expand the RHS *without* a constraint on the topic table
Expand Down Expand Up @@ -632,11 +650,11 @@ sub _hoist {

$result{sql} = _SELECT(
select => 'DISTINCT '
. _AS( _personality()->true() => $result{sel} ) . ",tid",
. _AS( $TRUE => $result{sel} ) . ",tid",
FROM => _AS( $union_sql, $union_alias ),
monitor => __LINE__
);
$result{type} = BOOLEAN;
$result{type} = $TRUE_TYPE;
$result{ignore_tid} = 0;
}
else {
Expand All @@ -662,8 +680,9 @@ sub _hoist {
);
my $where;
if ( $optype == BOOLEAN ) {
$where = $expr;
$expr = _personality()->true();
$where = $expr;
$expr = $TRUE;
$optype = $TRUE_TYPE;
}

my $ret_tid = "$lhs_alias.tid";
Expand Down Expand Up @@ -704,8 +723,9 @@ sub _hoist {

my $where;
if ( $optype == BOOLEAN ) {
$where = $expr;
$expr = _personality()->true();
$where = $expr;
$expr = $TRUE;
$optype = $TRUE_TYPE;
}

my $ret_tid = "$lhs_alias.tid";
Expand All @@ -721,7 +741,7 @@ sub _hoist {
select => _AS( $expr => $result{sel} ) . ",$ret_tid",
FROM => $tid_table . _AS( $lhs{sql} => $lhs_alias ),
WHERE => $where,
monitor => __LINE__
monitor => __LINE__ . " $op"
);
$result{type} = $optype;

Expand All @@ -743,8 +763,9 @@ sub _hoist {

my $where = '_IGNORE_';
if ( $optype == BOOLEAN ) {
$where = $expr;
$expr = _personality()->true();
$where = $expr;
$expr = $TRUE;
$optype = $TRUE_TYPE;
}

my $ret_tid = "$rhs_alias.tid";
Expand Down Expand Up @@ -786,8 +807,9 @@ sub _hoist {

my $where = '_IGNORE_';
if ( $optype == BOOLEAN ) {
$where = $expr;
$expr = _personality()->true();
$where = $expr;
$expr = $TRUE;
$optype = $TRUE_TYPE;
}
my $ret_tid = 'tid';
my $tid_table = '';
Expand Down Expand Up @@ -834,8 +856,11 @@ sub _cast {
if ( $type == NUMBER ) {
$arg = "$arg!=0";
}
elsif ( $type == PSEUDO_BOOL ) {
return "$arg=" . $TRUE;
}
else {
$arg = "$arg!=$cq$cq";
$arg = "$arg!=$CQ$CQ";
}
}
elsif ( $tgt_type == NUMBER ) {
Expand Down Expand Up @@ -1016,10 +1041,10 @@ sub _format_SQL {
my @ss = ();

# Replace escaped quotes
$sql =~ s/(\\$cq)/push(@ss,$1); "![$#ss]!"/ges;
$sql =~ s/(\\$CQ)/push(@ss,$1); "![$#ss]!"/ges;

# Replace quoted strings
$sql =~ s/($cq([^$cq])*$cq)/push(@ss,$1); "![$#ss]!"/ges;
$sql =~ s/($CQ([^$CQ])*$CQ)/push(@ss,$1); "![$#ss]!"/ges;

# Replace bracketed subexpressions
my $n = 0;
Expand Down
74 changes: 36 additions & 38 deletions lib/Foswiki/Contrib/DBIStoreContrib/Personality.pm
Expand Up @@ -12,16 +12,45 @@ use Assert;
# have a database personality module, which provides these custom
# operations in a consistent way.

=begin TML
{string_quote} is the quote character to use for character
strings - default is '
{true_value} and {true_type} For DB's that
don't support a true BOOLEAN type, the true_type can be PSEUDO_BOOL,
in which case boolean operations on the data will always be preceded
by =1.
{text_type} the name of the TEXT type, used to store variable-length
strings.
{requires_COMMIT} is 1 if the DB requires a COMMIT at the end of the
initial transaction.
The default is TRUE which works for SQLite, MySQL and Postgresql.
=cut

sub new {
my ( $class, $dbistore ) = @_;
my $this = bless( { store => $dbistore }, $class );
my $this = bless(
{
store => $dbistore,
requires_COMMIT => 1,
string_quote => "'",
text_type => 'TEXT',
true_value => '1=1',
true_type => Foswiki::Contrib::DBIStoreContrib::HoistSQL::BOOLEAN,
},
$class
);

# SQL reserved words. The following words are reserved in all of
# PostgresSQL, ANSI SQL, MySQL and SQLite so provide a good
# PostgresSQL, MySQL, SQLite and T-SQL so provide a good
# working basis. Personality modules should extend this list.
$this->reserve(
qw(
ALL ALTER AND AS ASC BETWEEN BY CASCADE CASE CHECK COLLATE COLUMN
ADD ALL ALTER AND AS ASC BETWEEN BY CASCADE CASE CHECK COLLATE COLUMN
CONSTRAINT CREATE CROSS CURRENT_DATE CURRENT_TIME CURRENT_TIMESTAMP
DEFAULT DELETE DESC DISTINCT DROP ELSE EXISTS FOR FOREIGN FROM GROUP
HAVING IN INDEX INNER INSERT INTO IS JOIN KEY LEFT LIKE NOT NULL ON OR
Expand Down Expand Up @@ -76,29 +105,6 @@ SQL

=begin TML
---++ require_COMMIT() -> $boolean
True if there is an automatic transaction opened that requires a commit.
The default is TRUE which works for SQLite, MySQL and Postgresql.
=cut

sub requires_COMMIT {
return 1;
}

=begin TML
---++ text_type() -> string
Get the name of the TEXT type, used to store variable-length strings.
=cut

sub text_type {
return 'TEXT';
}

=begin TML
---++ regexp($lhs, $rhs) -> $sql
Construct an SQL expression to execute the given regular expression
match.
Expand Down Expand Up @@ -226,37 +232,29 @@ Cast a datum to a character string type for comparison

sub cast_to_text {
my ( $this, $d ) = @_;
return "CAST(($d) AS " . $this->text_type() . ')';
return "CAST(($d) AS $this->{text_type})";
}

=begin TML
---++ string_quote() -> $quote_char
Quote character for character strings - default is '
---++ make_comment() -> $comment_string
Make a comment string
=cut

sub string_quote {
return "'";
}

sub make_comment {
my $this = shift;
return '/*' . join( ' ', @_ ) . '*/';
}

sub true {
return '1=1';
}

1;
__DATA__
Author: Crawford Currie http://c-dot.co.uk
Module of Foswiki - The Free and Open Source Wiki, http://foswiki.org/, http://Foswiki.org/
Copyright (C) 2013 Foswiki Contributors. All Rights Reserved.
Copyright (C) 2013-2014 Foswiki Contributors. All Rights Reserved.
Foswiki Contributors are listed in the AUTHORS file in the root
of this distribution. NOTE: Please extend that file, not this notice.
Expand Down

0 comments on commit 44438ec

Please sign in to comment.