diff --git a/lib/App/AltSQL/Model.pm b/lib/App/AltSQL/Model.pm index 707ce04..01771a6 100644 --- a/lib/App/AltSQL/Model.pm +++ b/lib/App/AltSQL/Model.pm @@ -35,4 +35,86 @@ sub execute_sql { return $sth; } +sub parse_sql_line { + my ($self, $line, $in_something) = @_; + + # first we parse to strip the strings and quotes + # if we are at the end of a line in a comment or string + # then we return the character that started it (i.e. ` ' " or /*) + # then assuming we're not in one of those we can then run this same + # regex to determine 1 or 0 + my @chars = split //, $line; + my @sanitized_string; + + $in_something //= ''; + my $last_char; + CHAR: while(my $char = shift @chars) + { + if($in_something) + { + if($last_char eq '\\' && $in_something =~ /["'`]/) { + # this character is escaped. lets ignore it. + $last_char = ''; + next CHAR; + } + if($char eq $in_something) + { + $in_something = ''; + } + if($in_something eq '/*' && $char eq '/' && $last_char eq '*') + { + $in_something = ''; + } + } + else + { + for my $start (qw/' " `/) + { + if($char eq $start) { + if($last_char eq '\\') { + last; + # it's escaped + } + $in_something = $start; + } + } + if($char eq '*') + { + if($last_char eq '/') + { + $in_something = '/*'; + pop @sanitized_string; + } + } + if($char eq '-') + { + if($last_char eq '-') + { + $in_something = '--'; + pop @sanitized_string; + } + } + unless($in_something) + { + push @sanitized_string, $char; + } + } + $last_char = $char; + } + if($in_something eq '--') + { + $in_something = ''; + } + return $in_something if $in_something; + + $line = join '', @sanitized_string; + print STDERR $line, "\n"; + if ($line =~ m{(;|\\G|\\c)\s*$} || $line =~ m{^\s*(quit|exit)\s*$} || $line =~ m{^\s*$}) { + return 1; + } + else { + return 0; + } +} + 1; diff --git a/lib/App/AltSQL/Term.pm b/lib/App/AltSQL/Term.pm index d00d244..44f40ef 100644 --- a/lib/App/AltSQL/Term.pm +++ b/lib/App/AltSQL/Term.pm @@ -79,9 +79,9 @@ sub _build_term { sub return_key { my $self = shift; - ## The user has pressed the 'enter' key. If the buffer ends in ';' or '\G', or if they've typed the bare word 'quit' or 'exit', accept the buffer my $input = join ' ', @{ $self->term->{lines} }; - if ($input =~ m{(;|\\G|\\c)\s*$} || $input =~ m{^\s*(quit|exit)\s*$} || $input =~ m{^\s*$}) { + my $ret = $self->app->model->parse_sql_line($input); + if ($ret eq '1') { $self->term->accept_line(); } else { diff --git a/t/005_sql_parse_line.t b/t/005_sql_parse_line.t new file mode 100644 index 0000000..728012c --- /dev/null +++ b/t/005_sql_parse_line.t @@ -0,0 +1,39 @@ +use strict; +use warnings; +use Test::More; +use Test::Deep; +use App::AltSQL::Model; + +ok my $model = App::AltSQL::Model->new(app => 1); + +is $model->parse_sql_line('test'), 0, 'Incomplete statement'; + +is $model->parse_sql_line('test;'), 1, 'Semicolon completes statement'; + +is $model->parse_sql_line('quit'), 1, 'quit statement'; + +is $model->parse_sql_line('exit'), 1, 'exit statement'; + +is $model->parse_sql_line(' '), 1, 'blank space statement'; + +is $model->parse_sql_line('test\G'), 1, '\G statement'; + +is $model->parse_sql_line('test\c'), 1, '\c statement'; + +is $model->parse_sql_line('select * from film where title = ";'), '"', 'Semi colon in string'; +is $model->parse_sql_line('";', '"'), 1, 'Tail end of statement where we were in a string'; + +is $model->parse_sql_line('insert into mytab values (";",'), 0, 'Incomplete statement'; + +is $model->parse_sql_line(q{select * from film where title = '\';}), "'", 'Semi colon in string'; + +is $model->parse_sql_line(q{select * from film where title = "\";}), '"', 'Semi colon in string'; + +is $model->parse_sql_line(q{select * from film where title = "\\\\";}), 1, 'Escaped slash, terminated string and end of statement'; + +is $model->parse_sql_line(q{select * from film where title = /* "\";}), '/*', 'Semi colon in comment'; +is $model->parse_sql_line(q{*/ 'test';}, '/*'), 1, 'Statement terminated after comment closes'; + +is $model->parse_sql_line(q{select 'test'; -- a simple statement}), 1, 'Statement terminated Got a comment after'; + +done_testing;