Moose Like

Its one more operator day here in the Moose-pen

Today I am finally going to get around to the last two of my operators the 'Like' and 'Not Like'. This is where we get into sort a sticky wicket and we are entering the realm of 'regular expression' queries which may work quite differently on the various SQL boxes and I know 100% different in MongoDB and other non SQL dbs. So what to do??

Well I might as well go with the standard SQL ones '%' and '_' meaning multiple and single characters, and the next question do I validate for their present in a 'Like' as this sql


WHERE first_name LIKE 'Bob'

is perfectly valid though not a very good query. I guess no validation then, just pass in what I am given on on the param. Unfortunately like most predicates almost any combination is allowed as

SELECT * FROM Customers where CustomerName like (select CustomerName from Customers where CustomerID in (4,5,1))

is valid.

The key here is to provide good documentation on the Database::Accessor side and the DBI::Driver side of how it will work. The normal way you would use it is to pass the 'regular expression' as a param to prevent SQL injection and I will have to make that perfectly clear in the documents.

Adopting the SQL standard will make things for my Driver::DBI as I have no reason to parse or translate the passed in expression, though I may be in for a tough time when I have to do that for MongoDB or another non SQL one. That is a much later post.

As always I start with a test;


{
caption => 'Like operator with regex',
key => 'conditions',
conditions => [
{
left => {
name => 'last_name',
view => 'people'
},
operator => 'Like',
right =>{value=>'Blog%'}
},
],
create => {
container => $container,
sql =>
"INSERT INTO people ( first_name, last_name ) VALUES( ?, ? )",
params => $params
},
retrieve => {
sql =>
"SELECT people.first_name, people.last_name, people.user_id FROM people WHERE people.last_name LIKE ?",
params => ['Blog%']
},

update => {
container => $container,
sql =>
"UPDATE people SET first_name = ?, last_name = ? WHERE people.last_name LIKE ?",
params => [ 'Bill', 'Bloggings','Blog%' ]
},
delete => {
sql => "DELETE FROM people WHERE people.id WHERE people.last_name LIKE ?",
params => ['Blog%']
},
},


and I think this code patch will handle both;

}
elsif ($predicate->operator eq Database::Accessor::Driver::DBI::SQL::LIKE
or $predicate->operator eq Database::Accessor::Driver::DBI::SQL::NOT_LIKE) {

$clause .= join(" ",$self->_field_sql($predicate->left,1)
,$predicate->operator
,$self->_field_sql($predicate->right,1)
);
}
elsif ($predicate->operator eq Database::Accessor::Driver::DBI::SQL::BETWEEN) {

Now I did get this error;

Bareword "Database::Accessor::Driver::DBI::SQL::NOT_LIKE"
not allowed while "strict subs" in use

I forgot to add 'NOT_LIKE' to my constants class so in it goes;

use constant LIKE =>'LIKE';
++use constant NOT_LIKE =>'NOT LIKE';
use constant IS_NULL =>'IS NULL';

and after that on my first run I just need to fix a typo in my expected SQL

# Expected SQL--> DELETE FROM people WHERE people.id WHERE people.last_name LIKE ?
# Generated SQL-> DELETE FROM people WHERE people.last_name LIKE ?

And I get a full pass. The next test where all I did was copy the frst one and change

-- operator => 'Like',
++ operator => 'Not Like',

and then of course my expected SQL and on my first run I got

Attribute (operator) does not pass the type constraint because: The Operator 'NOT Like',
is not a valid Accessor Operator! Try one of '!=', '<', '<=', '<>', '=', '>', '>=', 'AND', 'BETWEEN', 'IN',
'IS NOT NULL', 'IS NULL', 'LIKE', 'NOT IN', 'OR' at

opps need a little patch to Database::Accessor::Constants class as well;

use constant LIKE => 'LIKE';
++use constant NOT_LIKE => 'NOT LIKE';
use constant IS_NULL => 'IS NULL';

use constant OPERATORS => {
++ Database::Accessor::Constants::NOT_LIKE => 1,
Database::Accessor::Constants::IN => 1,


and on this run I get a full pass.

I could at this point add in a bunch of other tests but really I am not accomplishing much by doing this as all I would be doing it retesting the '_field_sql' sub over and over again.

What I was wrong about is that the there is some constraints for this operator, the left and right cannot be an Array-Ref as in these SQLs


WHERE (last_name,first_name) like 'Blogs%';
WHERE last_name like ('Blogs%','BILL%)

though oddly enough this is valid

WHERE 'Thomas' LIKE ('Thom%')

but useless as it is the same as

WHERE 1=1

but that's another post;

To fix this I added in this code to Driver::DBI as who knows if some other sort of DB my allow this form of params.


elsif ($predicate->operator eq Database::Accessor::Driver::DBI::SQL::LIKE
or $predicate->operator eq Database::Accessor::Driver::DBI::SQL::NOT_LIKE) {

++ die("$message '".$predicate->operator."' left can not be an Array Ref!")
++ if ( ref( $predicate->left()) eq 'ARRAY' );

++ die("$message '".$predicate->operator."' right can not be an Array Ref!")
++ if ( ref( $predicate->right()) eq 'ARRAY' );

and then 'exception' two tests to cover these off and I get

ok 94 - retrieve Like left must not be an array-ref
ok 95 - retrieve Like right must not be an array-ref

and I am done for the day.

DSC04348a.jpg


Leave a comment

About byterock

user-pic Long time Perl guy, a few CPAN mods allot of work on DBD::Oracle and a few YAPC presentations