... | ... |
@@ -1,3 +1,5 @@ |
1 |
+0.1667 |
|
2 |
+ - added EXPERIMENTAL reserved_word_quote attribute. |
|
1 | 3 |
0.1666 |
2 | 4 |
- removed from cache() and cache_method() document for a while and cache() value |
3 | 5 |
become 0 because I find something bug. |
... | ... |
@@ -55,6 +55,7 @@ __PACKAGE__->attr( |
55 | 55 |
models => sub { {} }, |
56 | 56 |
query_builder => sub { DBIx::Custom::QueryBuilder->new }, |
57 | 57 |
result_class => 'DBIx::Custom::Result', |
58 |
+ reserved_word_quote => '', |
|
58 | 59 |
safety_character => '\w', |
59 | 60 |
stash => sub { {} } |
60 | 61 |
); |
... | ... |
@@ -147,8 +148,10 @@ sub column { |
147 | 148 |
|
148 | 149 |
$columns ||= []; |
149 | 150 |
|
151 |
+ my $q = $self->reserved_word_quote; |
|
152 |
+ |
|
150 | 153 |
my @column; |
151 |
- push @column, "$table.$_ as ${table}__$_" for @$columns; |
|
154 |
+ push @column, "$q$table$q.$q$_$q as $q${table}${q}__$q$_$q" for @$columns; |
|
152 | 155 |
|
153 | 156 |
return join (', ', @column); |
154 | 157 |
} |
... | ... |
@@ -195,6 +198,14 @@ sub create_query { |
195 | 198 |
# Create query |
196 | 199 |
$query = $builder->build_query($source); |
197 | 200 |
|
201 |
+ # Bind |
|
202 |
+ my $columns = $query->columns; |
|
203 |
+ if (my $q = $self->reserved_word_quote) { |
|
204 |
+ foreach my $column (@$columns) { |
|
205 |
+ $column =~ s/$q//g; |
|
206 |
+ } |
|
207 |
+ } |
|
208 |
+ |
|
198 | 209 |
# Cache query |
199 | 210 |
$self->cache_method->($self, $source, |
200 | 211 |
{sql => $query->sql, |
... | ... |
@@ -245,6 +256,9 @@ our %DELETE_ARGS |
245 | 256 |
|
246 | 257 |
sub delete { |
247 | 258 |
my ($self, %args) = @_; |
259 |
+ |
|
260 |
+ # Quote for reserved word |
|
261 |
+ my $q = $self->reserved_word_quote; |
|
248 | 262 |
|
249 | 263 |
# Check argument names |
250 | 264 |
foreach my $name (keys %args) { |
... | ... |
@@ -273,7 +287,7 @@ sub delete { |
273 | 287 |
my @sql; |
274 | 288 |
|
275 | 289 |
# Delete |
276 |
- push @sql, "delete from $table $swhere"; |
|
290 |
+ push @sql, "delete from $q$table$q $swhere"; |
|
277 | 291 |
push @sql, $append if $append; |
278 | 292 |
|
279 | 293 |
my $sql = join(' ', @sql); |
... | ... |
@@ -396,6 +410,9 @@ sub each_column { |
396 | 410 |
sub execute{ |
397 | 411 |
my ($self, $query, %args) = @_; |
398 | 412 |
|
413 |
+ # Quote for reserved word |
|
414 |
+ my $q = $self->reserved_word_quote; |
|
415 |
+ |
|
399 | 416 |
# Check argument names |
400 | 417 |
foreach my $name (keys %args) { |
401 | 418 |
croak qq{Argument "$name" is invalid name} |
... | ... |
@@ -412,6 +429,11 @@ sub execute{ |
412 | 429 |
my $filter = {}; |
413 | 430 |
|
414 | 431 |
my $tables = $query->tables; |
432 |
+ if ($q) { |
|
433 |
+ foreach my $table (@$tables) { |
|
434 |
+ $table =~ s/$q//g; |
|
435 |
+ } |
|
436 |
+ } |
|
415 | 437 |
my $arg_tables = $args{table} || []; |
416 | 438 |
$arg_tables = [$arg_tables] |
417 | 439 |
unless ref $arg_tables eq 'ARRAY'; |
... | ... |
@@ -534,6 +556,9 @@ our %INSERT_ARGS = map { $_ => 1 } @COMMON_ARGS, qw/param append/; |
534 | 556 |
|
535 | 557 |
sub insert { |
536 | 558 |
my ($self, %args) = @_; |
559 |
+ |
|
560 |
+ # Quote for reserved word |
|
561 |
+ my $q = $self->reserved_word_quote; |
|
537 | 562 |
|
538 | 563 |
# Check argument names |
539 | 564 |
foreach my $name (keys %args) { |
... | ... |
@@ -553,6 +578,8 @@ sub insert { |
553 | 578 |
foreach my $column (keys %$param) { |
554 | 579 |
croak qq{"$column" is not safety column name} |
555 | 580 |
unless $column =~ /^[$safety\.]+$/; |
581 |
+ $column = "$q$column$q"; |
|
582 |
+ $column =~ s/\./$q.$q/; |
|
556 | 583 |
push @columns, $column; |
557 | 584 |
} |
558 | 585 |
|
... | ... |
@@ -560,7 +587,7 @@ sub insert { |
560 | 587 |
my @sql; |
561 | 588 |
|
562 | 589 |
# Insert |
563 |
- push @sql, "insert into $table {insert_param ". join(' ', @columns) . '}'; |
|
590 |
+ push @sql, "insert into $q$table$q {insert_param ". join(' ', @columns) . '}'; |
|
564 | 591 |
push @sql, $append if $append; |
565 | 592 |
|
566 | 593 |
# SQL |
... | ... |
@@ -734,9 +761,11 @@ sub model { |
734 | 761 |
sub mycolumn { |
735 | 762 |
my ($self, $table, $columns) = @_; |
736 | 763 |
|
764 |
+ my $q = $self->reserved_word_quote; |
|
765 |
+ |
|
737 | 766 |
$columns ||= []; |
738 | 767 |
my @column; |
739 |
- push @column, "$table.$_ as $_" for @$columns; |
|
768 |
+ push @column, "$q$table$q.$q$_$q as $q$_$q" for @$columns; |
|
740 | 769 |
|
741 | 770 |
return join (', ', @column); |
742 | 771 |
} |
... | ... |
@@ -787,6 +816,9 @@ our %SELECT_ARGS |
787 | 816 |
|
788 | 817 |
sub select { |
789 | 818 |
my ($self, %args) = @_; |
819 |
+ |
|
820 |
+ # Quote for reserved word |
|
821 |
+ my $q = $self->reserved_word_quote; |
|
790 | 822 |
|
791 | 823 |
# Check argument names |
792 | 824 |
foreach my $name (keys %args) { |
... | ... |
@@ -876,13 +908,13 @@ sub select { |
876 | 908 |
if ($relation) { |
877 | 909 |
my $found = {}; |
878 | 910 |
foreach my $table (@$tables) { |
879 |
- push @sql, ($table, ',') unless $found->{$table}; |
|
911 |
+ push @sql, ("$q$table$q", ',') unless $found->{$table}; |
|
880 | 912 |
$found->{$table} = 1; |
881 | 913 |
} |
882 | 914 |
} |
883 | 915 |
else { |
884 | 916 |
my $main_table = $tables->[-1] || ''; |
885 |
- push @sql, $main_table; |
|
917 |
+ push @sql, "$q$main_table$q"; |
|
886 | 918 |
} |
887 | 919 |
pop @sql if ($sql[-1] || '') eq ','; |
888 | 920 |
|
... | ... |
@@ -993,6 +1025,9 @@ our %UPDATE_ARGS |
993 | 1025 |
|
994 | 1026 |
sub update { |
995 | 1027 |
my ($self, %args) = @_; |
1028 |
+ |
|
1029 |
+ # Quote for reserved word |
|
1030 |
+ my $q = $self->reserved_word_quote; |
|
996 | 1031 |
|
997 | 1032 |
# Check argument names |
998 | 1033 |
foreach my $name (keys %args) { |
... | ... |
@@ -1008,20 +1043,19 @@ sub update { |
1008 | 1043 |
my $append = delete $args{append} || ''; |
1009 | 1044 |
my $allow_update_all = delete $args{allow_update_all}; |
1010 | 1045 |
|
1011 |
- # Update keys |
|
1012 |
- my @clumns = keys %$param; |
|
1013 |
- |
|
1014 | 1046 |
# Columns |
1015 | 1047 |
my @columns; |
1016 | 1048 |
my $safety = $self->safety_character; |
1017 | 1049 |
foreach my $column (keys %$param) { |
1018 | 1050 |
croak qq{"$column" is not safety column name} |
1019 | 1051 |
unless $column =~ /^[$safety\.]+$/; |
1020 |
- push @columns, $column; |
|
1052 |
+ $column = "$q$column$q"; |
|
1053 |
+ $column =~ s/\./$q.$q/; |
|
1054 |
+ push @columns, "$column"; |
|
1021 | 1055 |
} |
1022 | 1056 |
|
1023 | 1057 |
# Update clause |
1024 |
- my $update_clause = '{update_param ' . join(' ', @clumns) . '}'; |
|
1058 |
+ my $update_clause = '{update_param ' . join(' ', @columns) . '}'; |
|
1025 | 1059 |
|
1026 | 1060 |
# Where |
1027 | 1061 |
my $w = $self->_where($where); |
... | ... |
@@ -1037,7 +1071,7 @@ sub update { |
1037 | 1071 |
my @sql; |
1038 | 1072 |
|
1039 | 1073 |
# Update |
1040 |
- push @sql, "update $table $update_clause $swhere"; |
|
1074 |
+ push @sql, "update $q$table$q $update_clause $swhere"; |
|
1041 | 1075 |
push @sql, $append if $append; |
1042 | 1076 |
|
1043 | 1077 |
# Rearrange parameters |
... | ... |
@@ -1138,6 +1172,7 @@ sub where { |
1138 | 1172 |
return DBIx::Custom::Where->new( |
1139 | 1173 |
query_builder => $self->query_builder, |
1140 | 1174 |
safety_character => $self->safety_character, |
1175 |
+ reserved_word_quote => $self->reserved_word_quote, |
|
1141 | 1176 |
@_ |
1142 | 1177 |
); |
1143 | 1178 |
} |
... | ... |
@@ -1254,8 +1289,12 @@ sub _tables { |
1254 | 1289 |
my $tables = []; |
1255 | 1290 |
|
1256 | 1291 |
my $safety_character = $self->safety_character; |
1292 |
+ my $q = $self->reserved_word_quote; |
|
1293 |
+ my $q_re = quotemeta($q); |
|
1257 | 1294 |
|
1258 |
- while ($source =~ /\b($safety_character+)\./g) { |
|
1295 |
+ my $table_re = $q ? qr/\b$q_re?([$safety_character]+)$q_re?\./ |
|
1296 |
+ : qr/\b([$safety_character]+)\./; |
|
1297 |
+ while ($source =~ /$table_re/g) { |
|
1259 | 1298 |
push @$tables, $1; |
1260 | 1299 |
} |
1261 | 1300 |
|
... | ... |
@@ -1267,13 +1306,17 @@ sub _push_join { |
1267 | 1306 |
|
1268 | 1307 |
return unless @$join; |
1269 | 1308 |
|
1309 |
+ my $q = $self->reserved_word_quote; |
|
1310 |
+ |
|
1270 | 1311 |
my $tree = {}; |
1271 | 1312 |
|
1272 | 1313 |
for (my $i = 0; $i < @$join; $i++) { |
1273 | 1314 |
|
1274 | 1315 |
my $join_clause = $join->[$i]; |
1275 |
- |
|
1276 |
- if ($join_clause =~ /\s([^\.\s]+?)\..+\s([^\.\s]+?)\..+?$/) { |
|
1316 |
+ my $q_re = quotemeta($q); |
|
1317 |
+ my $join_re = $q ? qr/\s$q_re?([^\.\s$q_re]+?)$q_re?\..+\s$q_re?([^\.\s$q_re]+?)$q_re?\..+?$/ |
|
1318 |
+ : qr/\s([^\.\s]+?)\..+\s([^\.\s]+?)\..+?$/; |
|
1319 |
+ if ($join_clause =~ $join_re) { |
|
1277 | 1320 |
|
1278 | 1321 |
my $table1 = $1; |
1279 | 1322 |
my $table2 = $2; |
... | ... |
@@ -1305,7 +1348,13 @@ sub _where { |
1305 | 1348 |
my $w; |
1306 | 1349 |
if (ref $where eq 'HASH') { |
1307 | 1350 |
my $clause = ['and']; |
1308 |
- push @$clause, "{= $_}" for keys %$where; |
|
1351 |
+ my $q = $self->reserved_word_quote; |
|
1352 |
+ foreach my $column (keys %$where) { |
|
1353 |
+ $column = "$q$column$q"; |
|
1354 |
+ $column =~ s/\./$q.$q/; |
|
1355 |
+ push @$clause, "{= $column}" for keys %$where; |
|
1356 |
+ } |
|
1357 |
+ |
|
1309 | 1358 |
$w = $self->where(clause => $clause, param => $where); |
1310 | 1359 |
} |
1311 | 1360 |
elsif (ref $where eq 'DBIx::Custom::Where') { |
... | ... |
@@ -1596,6 +1645,13 @@ Password, used when C<connect()> is executed. |
1596 | 1645 |
|
1597 | 1646 |
Query builder, default to L<DBIx::Custom::QueryBuilder> object. |
1598 | 1647 |
|
1648 |
+=head2 C<reserved_word_quote> EXPERIMENTAL |
|
1649 |
+ |
|
1650 |
+ my reserved_word_quote = $dbi->reserved_word_quote; |
|
1651 |
+ $dbi = $dbi->reserved_word_quote('"'); |
|
1652 |
+ |
|
1653 |
+Quote for reserved word, default to empty string. |
|
1654 |
+ |
|
1599 | 1655 |
=head2 C<result_class> |
1600 | 1656 |
|
1601 | 1657 |
my $result_class = $dbi->result_class; |
... | ... |
@@ -16,6 +16,7 @@ push @DBIx::Custom::CARP_NOT, __PACKAGE__; |
16 | 16 |
__PACKAGE__->attr( |
17 | 17 |
[qw/param query_builder safety_character/], |
18 | 18 |
clause => sub { [] }, |
19 |
+ reserved_word_quote => '' |
|
19 | 20 |
); |
20 | 21 |
|
21 | 22 |
sub to_string { |
... | ... |
@@ -75,6 +76,10 @@ sub _parse { |
75 | 76 |
croak qq{Each tag contains one column name: tag "$clause"} |
76 | 77 |
unless @$columns == 1; |
77 | 78 |
my $column = $columns->[0]; |
79 |
+ if (my $q = $self->reserved_word_quote) { |
|
80 |
+ $column =~ s/$q//g; |
|
81 |
+ } |
|
82 |
+ |
|
78 | 83 |
my $safety = $self->safety_character; |
79 | 84 |
croak qq{"$column" is not safety column name} |
80 | 85 |
unless $column =~ /^[$safety\.]+$/; |
... | ... |
@@ -242,6 +242,15 @@ like($@, qr/noexist/, "invalid"); |
242 | 242 |
eval{$dbi->insert(table => 'table', param => {';' => 1})}; |
243 | 243 |
like($@, qr/safety/); |
244 | 244 |
|
245 |
+$dbi = DBIx::Custom->connect($NEW_ARGS->{0}); |
|
246 |
+$dbi->reserved_word_quote('"'); |
|
247 |
+$dbi->execute('create table "table" ("select")'); |
|
248 |
+$dbi->apply_filter('table', select => {out => sub { $_[0] * 2}}); |
|
249 |
+$dbi->insert(table => 'table', param => {select => 1}); |
|
250 |
+$result = $dbi->execute('select * from "table"'); |
|
251 |
+$rows = $result->fetch_hash_all; |
|
252 |
+is_deeply($rows, [{select => 2}], "reserved word"); |
|
253 |
+ |
|
245 | 254 |
test 'update'; |
246 | 255 |
$dbi = DBIx::Custom->connect($NEW_ARGS->{0}); |
247 | 256 |
$dbi->execute($CREATE_TABLE->{1}); |
... | ... |
@@ -331,6 +340,31 @@ like($@, qr/safety/); |
331 | 340 |
eval{$dbi->update(table => 'table1', param => {'key1' => 1}, where => {';' => 1})}; |
332 | 341 |
like($@, qr/safety/); |
333 | 342 |
|
343 |
+$dbi = DBIx::Custom->connect($NEW_ARGS->{0}); |
|
344 |
+$dbi->reserved_word_quote('"'); |
|
345 |
+$dbi->execute('create table "table" ("select", "update")'); |
|
346 |
+$dbi->apply_filter('table', select => {out => sub { $_[0] * 2}}); |
|
347 |
+$dbi->apply_filter('table', update => {out => sub { $_[0] * 3}}); |
|
348 |
+$dbi->insert(table => 'table', param => {select => 1}); |
|
349 |
+$dbi->update(table => 'table', where => {select => 1}, param => {update => 2}); |
|
350 |
+$result = $dbi->execute('select * from "table"'); |
|
351 |
+$rows = $result->fetch_hash_all; |
|
352 |
+is_deeply($rows, [{select => 2, update => 6}], "reserved word"); |
|
353 |
+ |
|
354 |
+eval {$dbi->update_all(table => 'table', param => {';' => 2}) }; |
|
355 |
+like($@, qr/safety/); |
|
356 |
+ |
|
357 |
+$dbi = DBIx::Custom->connect($NEW_ARGS->{0}); |
|
358 |
+$dbi->reserved_word_quote('"'); |
|
359 |
+$dbi->execute('create table "table" ("select", "update")'); |
|
360 |
+$dbi->apply_filter('table', select => {out => sub { $_[0] * 2}}); |
|
361 |
+$dbi->apply_filter('table', update => {out => sub { $_[0] * 3}}); |
|
362 |
+$dbi->insert(table => 'table', param => {select => 1}); |
|
363 |
+$dbi->update(table => 'table', where => {'table.select' => 1}, param => {update => 2}); |
|
364 |
+$result = $dbi->execute('select * from "table"'); |
|
365 |
+$rows = $result->fetch_hash_all; |
|
366 |
+is_deeply($rows, [{select => 2, update => 6}], "reserved word"); |
|
367 |
+ |
|
334 | 368 |
test 'update_all'; |
335 | 369 |
$dbi = DBIx::Custom->connect($NEW_ARGS->{0}); |
336 | 370 |
$dbi->execute($CREATE_TABLE->{1}); |
... | ... |
@@ -411,6 +445,16 @@ like($@, qr/"where" must be specified/, |
411 | 445 |
eval{$dbi->delete(table => 'table1', where => {';' => 1})}; |
412 | 446 |
like($@, qr/safety/); |
413 | 447 |
|
448 |
+$dbi = DBIx::Custom->connect($NEW_ARGS->{0}); |
|
449 |
+$dbi->reserved_word_quote('"'); |
|
450 |
+$dbi->execute('create table "table" ("select", "update")'); |
|
451 |
+$dbi->apply_filter('table', select => {out => sub { $_[0] * 2}}); |
|
452 |
+$dbi->insert(table => 'table', param => {select => 1}); |
|
453 |
+$dbi->delete(table => 'table', where => {select => 1}); |
|
454 |
+$result = $dbi->execute('select * from "table"'); |
|
455 |
+$rows = $result->fetch_hash_all; |
|
456 |
+is_deeply($rows, [], "reserved word"); |
|
457 |
+ |
|
414 | 458 |
test 'delete_all'; |
415 | 459 |
$dbi = DBIx::Custom->connect($NEW_ARGS->{0}); |
416 | 460 |
$dbi->execute($CREATE_TABLE->{0}); |
... | ... |
@@ -468,6 +512,14 @@ is_deeply($rows, [{table1_key1 => 1, table2_key1 => 1, key2 => 2, key3 => 5}], " |
468 | 512 |
eval{$dbi->select(table => 'table1', noexist => 1)}; |
469 | 513 |
like($@, qr/noexist/, "invalid"); |
470 | 514 |
|
515 |
+$dbi = DBIx::Custom->connect($NEW_ARGS->{0}); |
|
516 |
+$dbi->reserved_word_quote('"'); |
|
517 |
+$dbi->execute('create table "table" ("select", "update")'); |
|
518 |
+$dbi->apply_filter('table', select => {out => sub { $_[0] * 2}}); |
|
519 |
+$dbi->insert(table => 'table', param => {select => 1, update => 2}); |
|
520 |
+$result = $dbi->select(table => 'table', where => {select => 1}); |
|
521 |
+$rows = $result->fetch_hash_all; |
|
522 |
+is_deeply($rows, [{select => 2, update => 2}], "reserved word"); |
|
471 | 523 |
|
472 | 524 |
test 'fetch filter'; |
473 | 525 |
$dbi = DBIx::Custom->connect($NEW_ARGS->{0}); |
... | ... |
@@ -1816,6 +1868,20 @@ $rows = $dbi->select( |
1816 | 1868 |
)->fetch_hash_all; |
1817 | 1869 |
is_deeply($rows, [{table1__key1 => 1}]); |
1818 | 1870 |
|
1871 |
+$dbi = DBIx::Custom->connect($NEW_ARGS->{0}); |
|
1872 |
+$dbi->reserved_word_quote('"'); |
|
1873 |
+$dbi->execute($CREATE_TABLE->{0}); |
|
1874 |
+$dbi->insert(table => 'table1', param => {key1 => 1, key2 => 2}); |
|
1875 |
+$dbi->execute($CREATE_TABLE->{2}); |
|
1876 |
+$dbi->insert(table => 'table2', param => {key1 => 1, key3 => 5}); |
|
1877 |
+$rows = $dbi->select( |
|
1878 |
+ table => 'table1', |
|
1879 |
+ column => '"table1"."key1" as "table1_key1", "table2"."key1" as "table2_key1", "key2", "key3"', |
|
1880 |
+ where => {'table1.key2' => 2}, |
|
1881 |
+ join => ['left outer join "table2" on "table1"."key1" = "table2"."key1"'], |
|
1882 |
+)->fetch_hash_all; |
|
1883 |
+is_deeply($rows, [{table1_key1 => 1, table2_key1 => 1, key2 => 2, key3 => 5}], |
|
1884 |
+ 'reserved_word_quote'); |
|
1819 | 1885 |
|
1820 | 1886 |
test 'model join and column attribute and all_column option'; |
1821 | 1887 |
{ |