Skip to content

Commit

Permalink
Parsing Line Termination - Issue ewaters#20
Browse files Browse the repository at this point in the history
Introducting a new method in the Model to parse a line from the command
prompt and determine whether the command is complete.

For example - altsql> select * from film where title = ";
";

This failed because it stops at the end of the line even though we
are inside quotation marks.
  • Loading branch information
Matt Church committed May 17, 2017
1 parent 889470a commit 2fcb2b3
Show file tree
Hide file tree
Showing 3 changed files with 110 additions and 2 deletions.
70 changes: 70 additions & 0 deletions lib/App/AltSQL/Model.pm
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,74 @@ sub execute_sql {
return $sth;
}

sub is_end_of_statement {
my ($self, $line) = @_;

# first we parse to strip the strings and quotes
# to prevent characters like ; appearing within strings
# from making us incorrectly detect the end of the
# statement.
my @chars = split //, $line;
my @sanitized_string;

my $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 0 if $in_something;

$line = join '', @sanitized_string;
# If the buffer ends in ';' or '\G', or
# if they've typed the bare word 'quit' or 'exit', accept the buffer
if ($line =~ m{(;|\\G|\\c)\s*$} || $line =~ m{^\s*(quit|exit)\s*$} || $line =~ m{^\s*$}) {
return 1;
}
else {
return 0;
}
}

1;
3 changes: 1 addition & 2 deletions lib/App/AltSQL/Term.pm
Original file line number Diff line number Diff line change
Expand Up @@ -79,9 +79,8 @@ 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*$}) {
if ($self->app->model->is_end_of_statement($input)) {
$self->term->accept_line();
}
else {
Expand Down
39 changes: 39 additions & 0 deletions t/005_sql_parse_line.t
Original file line number Diff line number Diff line change
@@ -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->is_end_of_statement('test'), 0, 'Incomplete statement';

is $model->is_end_of_statement('test;'), 1, 'Semicolon completes statement';

is $model->is_end_of_statement('quit'), 1, 'quit statement';

is $model->is_end_of_statement('exit'), 1, 'exit statement';

is $model->is_end_of_statement(' '), 1, 'blank space statement';

is $model->is_end_of_statement('test\G'), 1, '\G statement';

is $model->is_end_of_statement('test\c'), 1, '\c statement';

is $model->is_end_of_statement('select * from film where title = ";'), 0, 'Semi colon in string';
is $model->is_end_of_statement(qq{select * from film where title = ";\n";}), 1, 'Tail end of statement where we were in a string';

is $model->is_end_of_statement('insert into mytab values (";",'), 0, 'Incomplete statement';

is $model->is_end_of_statement(q{select * from film where title = '\';}), 0, 'Semi colon in string';

is $model->is_end_of_statement(q{select * from film where title = "\";}), 0, 'Semi colon in string';

is $model->is_end_of_statement(q{select * from film where title = "\\\\";}), 1, 'Escaped slash, terminated string and end of statement';

is $model->is_end_of_statement(q{select * from film where title = /* "\";}), 0, 'Semi colon in comment';
is $model->is_end_of_statement(qq{select * from film where title = /* "\";\n*/ 'test';}), 1, 'Statement terminated after comment closes';

is $model->is_end_of_statement(q{select 'test'; -- a simple statement}), 1, 'Statement terminated Got a comment after';

done_testing;

0 comments on commit 2fcb2b3

Please sign in to comment.