... | ... |
@@ -95,8 +95,11 @@ sub create_sql { |
95 | 95 |
} |
96 | 96 |
|
97 | 97 |
sub query { |
98 |
- my $self = shift; |
|
99 |
- my ($sql, @bind) = $self->creqte_sql(@_); |
|
98 |
+ my ($self, $template, $values, $filter) = @_; |
|
99 |
+ |
|
100 |
+ $filter ||= $self->bind_filter; |
|
101 |
+ |
|
102 |
+ my ($sql, @bind) = $self->creqte_sql($template, $values, $filter); |
|
100 | 103 |
$self->prepare($sql); |
101 | 104 |
$self->execute(@bind); |
102 | 105 |
} |
... | ... |
@@ -112,13 +115,93 @@ Object::Simple->build_class; |
112 | 115 |
package DBI::Custom::SQLTemplate; |
113 | 116 |
use Object::Simple; |
114 | 117 |
|
118 |
+### Attributes; |
|
119 |
+sub tag_start : Attr { default => '{' } |
|
120 |
+sub tag_end : Attr { default => '}' } |
|
121 |
+sub template : Attr {}; |
|
122 |
+sub tree : Attr { auto_build => sub { shift->tree([]) } } |
|
123 |
+ |
|
124 |
+ |
|
115 | 125 |
sub create_sql { |
126 |
+ my ($self, $template, $values, $filter) = @_; |
|
127 |
+ |
|
128 |
+ $self->parse($template); |
|
129 |
+ |
|
130 |
+ my ($sql, @bind); |
|
131 |
+ |
|
132 |
+ return ($sql, @bind); |
|
133 |
+} |
|
134 |
+ |
|
135 |
+our $TAG_SYNTAX = <<'EOS'; |
|
136 |
+[tag] [expand] |
|
137 |
+{= name} name = ? |
|
138 |
+{!= name} name != ? |
|
139 |
+ |
|
140 |
+{< name} name < ? |
|
141 |
+{> name} name > ? |
|
142 |
+{>= name} name >= ? |
|
143 |
+{<= name} name <= ? |
|
144 |
+ |
|
145 |
+{like name} name like ? |
|
146 |
+{in name} name in [?, ?, ..] |
|
147 |
+ |
|
148 |
+{insert_values} (key1, key2, key3) values (?, ?, ?) |
|
149 |
+{update_values} set key1 = ?, key2 = ?, key3 = ? |
|
150 |
+EOS |
|
151 |
+ |
|
152 |
+our %VALID_TAG_NAMES = map {$_ => 1} qw/=/; |
|
153 |
+sub parse { |
|
154 |
+ my ($self, $template) = @_; |
|
155 |
+ $self->template($template); |
|
156 |
+ |
|
157 |
+ # Clean start; |
|
158 |
+ delete $self->{tree}; |
|
159 |
+ |
|
160 |
+ # Tags |
|
161 |
+ my $tag_start = quotemeta $self->tag_start; |
|
162 |
+ my $tag_end = quotemeta $self->tag_end; |
|
116 | 163 |
|
164 |
+ # Tokenize |
|
165 |
+ my $state = 'text'; |
|
166 |
+ |
|
167 |
+ # Save original template |
|
168 |
+ my $original_template = $template; |
|
169 |
+ |
|
170 |
+ # Text |
|
171 |
+ while ($template =~ s/([^$tag_start]*?)$tag_start([^$tag_end].*?)$tag_end//sm) { |
|
172 |
+ my $text = $1; |
|
173 |
+ my $tag = $2; |
|
174 |
+ |
|
175 |
+ push @{$self->tree}, ['text', $text] if $text; |
|
176 |
+ |
|
177 |
+ if ($tag) { |
|
178 |
+ |
|
179 |
+ my ($tag_name, @params) = split /\s+/, $tag; |
|
180 |
+ |
|
181 |
+ croak("Tag name is empty in '$tag'.\n" . |
|
182 |
+ "Tag Syntax\n$TAG_SYNTAX.\n" . |
|
183 |
+ "Your SQL template is \n$original_template") |
|
184 |
+ unless length $tag_name; |
|
185 |
+ |
|
186 |
+ croak("Tag name '$tag_name' in '$tag' is invalid.\n" . |
|
187 |
+ "Tag Syntax\n$TAG_SYNTAX.\n" . |
|
188 |
+ "Your SQL template is \n$original_template") |
|
189 |
+ unless $VALID_TAG_NAMES{$tag_name}; |
|
190 |
+ |
|
191 |
+ push @{$self->tree}, [$tag_name, @params]; |
|
192 |
+ } |
|
193 |
+ } |
|
194 |
+ |
|
195 |
+ push @{$self->tree}, ['text', $template] if $template; |
|
117 | 196 |
} |
118 | 197 |
|
119 | 198 |
|
120 | 199 |
|
121 | 200 |
|
201 |
+ |
|
202 |
+ |
|
203 |
+ |
|
204 |
+ |
|
122 | 205 |
Object::Simple->build_class; |
123 | 206 |
|
124 | 207 |
=head1 NAME |
... | ... |
@@ -172,6 +172,16 @@ our ($U, $P, $D) = connect_info(); |
172 | 172 |
like($@, qr/connect_info 'no_exist' is invald/, 'no exist'); |
173 | 173 |
} |
174 | 174 |
|
175 |
+{ |
|
176 |
+ my $dbi = DBI::Custom->new; |
|
177 |
+ my $tmpl = "select * from table where {= title};"; |
|
178 |
+ my $values = {title => 'a'}; |
|
179 |
+ my ($sql, @bind) = $dbi->create_sql($tmpl, $values); |
|
180 |
+ is($sql, "select * from table where title = ?;"); |
|
181 |
+ is_deeply(\@bind, ['a']); |
|
182 |
+ |
|
183 |
+} |
|
184 |
+ |
|
175 | 185 |
sub connect_info { |
176 | 186 |
my $file = 'password.tmp'; |
177 | 187 |
open my $fh, '<', $file |