3 # Copyright (C) 2010 Guillaume Cottenceau and MNC S.A.
5 # This file is part of sdbl4j, and is licensed under the Apache 2.0 license.
13 for my $input (@ARGV) {
17 my $parser = XML::LibXML->new();
18 my $doc = $parser->parse_file($input);
21 my ($doc, $name) = @_;
23 foreach my $element ($doc->getElementsByTagName($name)) {
25 my $retval = { attributes => $attributes };
26 if ($element->getFirstChild) {
27 $retval->{cdata} = $element->getFirstChild->getData;
29 foreach my $attr ($element->attributes) {
30 $attributes->{$attr->name} = $attr->getValue;
32 push @retval, $retval;
37 my @sql = get_data($doc, 'sql');
38 my @variation = get_data($doc, 'variation');
39 my @db = get_data($doc, 'db');
40 my @in = get_data($doc, 'in');
41 my @out = get_data($doc, 'out');
42 my @multi = get_data($doc, 'multiRows');
43 my @addableSql = get_data($doc, 'addableSql');
44 my @implements = get_data($doc, 'implements');
45 my @outsugar = get_data($doc, 'outsugar');
46 my @postOrdering = get_data($doc, 'postOrdering');
47 @postOrdering && !@multi and die "$input: <postOrdering> without <multiRows> makes no sense.\n";
49 my $logerror = get_data($doc, 'noErrorLog') ? "log.debug" : "log.error";
51 foreach my $out (@out) {
52 my $colname = $out->{attributes}{name};
53 $colname =~ s/([a-z])([A-Z])/${1}_$2/g;
54 $out->{colname} = lc($colname);
58 print STDERR "sql: $sql\ndb: $db\n"
59 . Data::Dumper->Dump([\@in], ['in'])
60 . Data::Dumper->Dump([\@out], ['out']);
66 die "$input: there should be one <sql> element\n";
68 my $sql = $sql[0]{cdata};
69 if ($sql =~ /##VARIATIONS##/ && @variation < 2) {
70 die "$input: at least two <variation> elements make sense when there is ##VARIATION## in SQL\n";
73 die "$input: there should be one <db> element\n";
75 my $db = $db[0]{cdata};
76 my $implements = @implements ? $implements[0]{cdata} : '';
78 my $parameters_amount = 0;
79 $parameters_amount++ while $sql =~ /\?/g;
81 foreach my $param (@in, @out) {
82 if (!member($param->{attributes}{type}, qw(Boolean Integer BigDecimal Double Long String Timestamp Date byte[] Array Object[] String[]))) {
83 die "$input: $param->{attributes}{name}: '$param->{attributes}{type}' is not a supported type\n";
85 if ($param->{attributes}{name} !~ /^[a-zA-Z_0-9]+$/) {
86 die "$input: $param->{attributes}{name}: should be all alphanumerical or underscore\n";
88 while ($param->{attributes}{parameter} =~ /(\d+)/g) {
89 if ($1 < 1 || $1 > $parameters_amount) {
90 die "$input: $param->{attributes}{name}: '$1' is not in the parameters amount range (1..$parameters_amount)\n";
93 #- make sure we don't accidentally hit a reserved word
94 if (member($param->{attributes}{name}, qw(public protected private static transient volatile class this void))) {
95 $param->{attributes}{varname} = '_' . $param->{attributes}{name};
97 $param->{attributes}{varname} = $param->{attributes}{name};
101 #- check there are no unset in parameters
103 foreach my $paramnumber (1 .. $parameters_amount) {
104 (grep { $_->{attributes}{parameter} =~ /\b$paramnumber\b/ } @in) > 0
105 or die "$input: parameter number '$paramnumber' set by no 'in' parameter\n";
110 my $kind = @out == 0 ? 'update' : @multi == 0 ? 'uniqueSelect' : 'multiSelect';
112 my $package = $input;
113 $package =~ s|.*\borg\b|org|;
114 $package =~ s|/[^/]+$||;
119 $output .= "package $package;
121 import org.apache.log4j.Logger;
123 import java.sql.PreparedStatement;
124 import java.sql.SQLException;
125 import java.sql.ResultSet;
127 if ((grep { $_->{attributes}{type} eq 'Timestamp' } @in, @out) > 0) {
128 $output .= "import java.sql.Timestamp;\n";
130 if ((grep { $_->{attributes}{type} eq 'BigDecimal' } @in, @out) > 0) {
131 $output .= "import java.math.BigDecimal;\n";
133 if ((grep { $_->{attributes}{type} eq 'Array' } @in, @out) > 0) {
134 $output .= "import java.sql.Array;\n";
136 if ((grep { $_->{attributes}{type} eq 'Date' } @in, @out) > 0) {
137 $output .= "import java.util.Date;\n";
140 $output .= "import java.util.Collections;\n";
142 $output .= "\nimport org.gc.sdbl4j.DBUtils;\n";
143 if ($kind eq 'update') {
144 $output .= "import org.gc.sdbl4j.BatchHelper;\n";
145 $output .= "import org.gc.sdbl4j.BatchedUpdateHandle;\n";
147 $output .= "import org.gc.sdbl4j.DBSelectHelpers;\n";
149 my $motherclass = $kind eq 'update' ? 'DBUpdateService'
150 : $kind eq 'uniqueSelect' ? 'DBSelectService'
151 : 'DBSelectMultiService';
152 $output .= "import org.gc.sdbl4j.DBConnectionPool;
153 import org.gc.sdbl4j.$motherclass;
157 (my $classname = basename($input)) =~ s/\..*//;
159 $output .= "/*************************************************************************
160 * !!! IMPORTANT !!! DO NOT MODIFY THIS FILE, IT HAS BEEN AUTOGENERATED! *
161 *************************************************************************/
162 public class $classname extends $motherclass";
163 if ($kind eq 'multiSelect') {
164 $output .= "<$classname.RowContainer>";
168 $output .= " implements " . trim($implements);
171 $sql =~ s/\n/" +\n "/g;
175 private static Logger log = Logger.getLogger( $classname.class );
177 private static final String sql =
183 $output .= " public enum Variation { " . join(", ", map { $_->{attributes}{name} } @variation) . " };
185 private static String getSql( Variation variation ) {\n";
186 foreach my $variation (@variation) {
187 $output .= " if ( variation == Variation." . $variation->{attributes}{name} . " ) {
188 return sql.replaceFirst( \"##VARIATIONS##\", \"$variation->{cdata}\" );
191 $output .= " log.error( \"unknown variation \" + variation );
196 if ($kind eq 'update') {
197 $output .= " /**\n * Perform the database request, return the number of rows modified or -1 or error.\n";
198 } elsif ($kind eq 'uniqueSelect') {
199 $output .= " /**\n * Perform the database request, return a container for the received data, or <code>null</code> if the query
200 * didn't return any rows.\n";
202 $output .= " /**\n * Perform the database request, return a container holding all the received rows. You must call
203 * {\@link #next()} to iterate over the rows.\n";
208 if ($kind eq 'update') {
209 $output .= " public static int perform(";
211 $output .= " public static $classname perform(";
214 $output .= " String sqlAddition";
220 $output .= " Variation variation";
223 my $perform_params = join(', ', map { $_->{attributes}{type} . ' ' . $_->{attributes}{varname} } @in);
224 if ($perform_params) {
225 if (@addableSql || @variation) {
228 $output .= " $perform_params ";
230 if (@addableSql || @variation) {
235 my $get_connection_indent = ' ' x length(" ps = DBConnectionPool.getConnection( \"$db\" ).prepareStatement(");
238 /*************************************************************************
239 * !!! IMPORTANT !!! DO NOT MODIFY THIS FILE, IT HAS BEEN AUTOGENERATED! *
240 *************************************************************************/
241 PreparedStatement ps = null;
244 my $sql = @variation ? "getSql( variation )" : "sql";
247 ps = DBConnectionPool.getConnection( \"$db\" ).prepareStatement( $sql + sqlAddition,";
250 ps = DBConnectionPool.getConnection( \"$db\" ).prepareStatement( $sql,";
253 $get_connection_indent ResultSet.TYPE_SCROLL_INSENSITIVE,
254 $get_connection_indent ResultSet.CONCUR_READ_ONLY );
257 $output .= $ps_creator;
259 my %simpletypes = (Integer => { sqltype => 'INTEGER', pssetter => 'Int' },
260 Long => { sqltype => 'BIGINT', pssetter => 'Long' },
261 Double => { sqltype => 'DOUBLE', pssetter => 'Double' },
262 Boolean => { sqltype => 'BOOLEAN', pssetter => 'Boolean' });
266 my %type2getter = ('Boolean' => 'getBoolean', 'Integer' => 'getInteger', 'BigDecimal' => 'getBigDecimal', 'Double' => 'getDouble',
267 'Long' => 'getLong', 'String' => 'getString', 'Timestamp' => 'getTimestamp',
268 'Date' => 'getDate', 'byte[]' => 'getBytes', 'Array' => 'getArray', 'Object[]' => 'getArrayAsObjectArray',
269 'String[]' => 'getArrayAsStringArray' );
271 foreach my $param (@in) {
272 while ($param->{attributes}{parameter} =~ /(\d+)/g) {
274 if (member($param->{attributes}{type}, keys %simpletypes)) {
275 $output =~ /java.sql.Types/ or $output =~ s/(import java.sql.ResultSet;)/$1\nimport java.sql.Types;/;
276 $params .= " if ( $param->{attributes}{varname} == null ) {\n"
277 . " ps.setNull( $index, Types.$simpletypes{$param->{attributes}{type}}{sqltype} );\n"
279 . " ps.set$simpletypes{$param->{attributes}{type}}{pssetter}( $index, $param->{attributes}{varname} );\n"
281 } elsif ($param->{attributes}{type} eq 'byte[]') {
282 $params .= " ps.setBytes( $index, $param->{attributes}{varname} );\n";
283 } elsif ($param->{attributes}{type} eq 'Date') {
284 $params .= " ps.setDate( $index, new java.sql.Date( $param->{attributes}{varname}.getTime() ) );\n";
286 $params .= " ps.set$param->{attributes}{type}( $index, $param->{attributes}{varname} );\n";
293 if ($kind eq 'update') {
294 $output .= " int result = executeUpdate( ps );
297 } catch ( SQLException se ) {
298 String thissql = ps == null ? \"\" : ps.toString().replaceAll( \"\\\\s+\", \" \" );
299 $logerror( \"failed to perform SQL request\" + thissql + \"\\n\" + DBUtils.prettyPrint( se ) );
305 * Get a BatchedUpdateHandle object suitable for then sending updates in batch.
307 public static BatchedUpdateHandle<$classname> getBatchedUpdateHandle(";
309 $output .= " String sqlAddition ";
312 PreparedStatement ps = null;
314 $output .= $ps_creator;
315 $output .= " return new BatchedUpdateHandle<$classname>( ps );
316 } catch ( SQLException se ) {
317 log.error( \"failed to created batched update handle\\n\" + DBUtils.prettyPrint( se ) );
323 * Add an update for a given BatchedUpdateHandle object.
324 * WARNING: do not let too many updates accumulate before performing the
325 * batched update, because of memory exhaustion risk.
327 public static void addBatchedUpdate( BatchedUpdateHandle<$classname> handle";
328 if ($perform_params) {
329 $output .= ", $perform_params ";
334 PreparedStatement ps = handle.getPreparedStatement();
338 $output .= " ps.addBatch();
339 } catch ( SQLException se ) {
340 log.error( \"failed to add batched update\\n\" + DBUtils.prettyPrint( se ) );
345 * Perform the updates added to the given BatchedUpdateHandle object.
346 * WARNING: do not let too many updates accumulate before performing the
347 * batched update, because of memory exhaustion risk.
349 public static int[] performBatchedUpdate( BatchedUpdateHandle<$classname> handle ) {
351 return BatchHelper.performBatchedUpdates( handle.getPreparedStatement() );
352 } catch ( SQLException se ) {
353 log.error( \"failed to perform batched updates\\n\" + DBUtils.prettyPrint( se ) );
358 private $classname() {}
362 } elsif ($kind eq 'uniqueSelect') {
363 my $indent = ' ' x length($classname);
364 $output .= " ResultSet rs = executeQuery( ps );
367 = new $classname( " . join(",\n $indent ",
368 map { "DBSelectHelpers.$type2getter{$_->{attributes}{type}}( \"$_->{colname}\", rs )" } @out)
375 } catch ( SQLException se ) {
376 String thissql = ps == null ? \"\" : ps.toString().replaceAll( \"\\\\s+\", \" \" );
377 $logerror( \"failed to perform SQL request\" + thissql + \"\\n\" + DBUtils.prettyPrint( se ) );
382 foreach my $param (@out) {
383 $output .= " private $param->{attributes}{type} $param->{attributes}{varname};\n";
387 private $classname( " . join(', ', map { $_->{attributes}{type} . ' ' . $_->{attributes}{varname} } @out) . " ) {\n";
388 foreach my $param (@out) {
389 $output .= " this.$param->{attributes}{varname} = $param->{attributes}{varname};\n";
392 foreach my $param (@out) {
393 my $methname = $param->{attributes}{name};
394 $methname =~ s/(.)/uc($1)/e;
395 $methname =~ s/_(.)/uc($1)/ge;
396 $output .= " public $param->{attributes}{type} get$methname() {
397 return $param->{attributes}{varname};
401 foreach my $param (@outsugar) {
402 my $methname = $param->{attributes}{name};
403 $methname =~ s/(.)/uc($1)/e;
404 $methname =~ s/_(.)/uc($1)/ge;
405 $output .= " public $param->{attributes}{type} get$methname" . ($methname =~ /\(/ ? '' : '()') . " {\n";
406 if ($param->{attributes}{code} =~ /return/) {
407 $output .= $param->{attributes}{code};
409 $output .= " return $param->{attributes}{code};\n";
416 $output .= " ResultSet rs = executeQuery( ps );
417 $classname ret = new $classname();
418 while ( rs.next() ) {
419 ret.add( " . join(",\n ",
420 map { "DBSelectHelpers.$type2getter{$_->{attributes}{type}}( \"$_->{colname}\", rs )" } @out)
426 Collections.sort( ret.rows );";
430 } catch ( SQLException se ) {
431 String thissql = ps == null ? \"\" : ps.toString().replaceAll( \"\\\\s+\", \" \" );
432 $logerror( \"failed to perform SQL request\" + thissql + \"\\n\" + DBUtils.prettyPrint( se ) );
438 protected class RowContainer implements Comparable<RowContainer> {\n";
441 protected class RowContainer {\n";
443 foreach my $param (@out) {
444 $output .= " public $param->{attributes}{type} $param->{attributes}{varname};\n";
446 $output .= " public RowContainer( " . join(', ', map { "$_->{attributes}{type} $_->{attributes}{varname}" } @out) . " ) {\n";
447 foreach my $param (@out) {
448 $output .= " this.$param->{attributes}{varname} = $param->{attributes}{varname};\n";
453 public int compareTo( RowContainer o ) {\n";
454 if ($postOrdering[0]{attributes}{code} =~ /\breturn\b/) {
455 $output .= " $postOrdering[0]{attributes}{code}\n";
457 $output .= " return " . $postOrdering[0]{attributes}{code} . ";\n";
465 private $classname() {}\n\n";
467 $output .= " private void add( " . join(', ', map { $_->{attributes}{type} . ' ' . $_->{attributes}{varname} } @out) . " ) {
468 rows.add( new RowContainer( " . join(', ', map { $_->{attributes}{varname} } @out) . " ) );
471 foreach my $param (@out) {
472 my $methname = $param->{attributes}{name};
473 $methname =~ s/(.)/uc($1)/e;
474 $methname =~ s/_(.)/uc($1)/ge;
475 $output .= " public $param->{attributes}{type} get$methname() {
476 if ( currentRow == null ) {
477 log.error( \"No current row! #next wasn't called? row number = \" + getRow() + \"; rows = \" + getRows() + \"\\n\"
478 + DBUtils.backtrace() );
481 return currentRow.$param->{attributes}{varname};
485 foreach my $param (@outsugar) {
486 my $methname = $param->{attributes}{name};
487 $methname =~ s/(.)/uc($1)/e;
488 $methname =~ s/_(.)/uc($1)/ge;
489 $output .= " public $param->{attributes}{type} get$methname " . ($methname =~ /\(/ ? '' : '()') . " {
490 if ( currentRow == null ) {
491 log.error( \"No current row! #next wasn't called? row number = \" + getRow() + \"; rows = \" + getRows() + \"\\n\"
492 + DBUtils.backtrace() );
495 if ($param->{attributes}{code} =~ /return/) {
496 $output .= $param->{attributes}{code};
498 $output .= " return $param->{attributes}{code};\n";
506 $file =~ s/\.db\.xml$/\.java/;
507 output($file, $output);
511 sub member { my $e = shift; foreach (@_) { $e eq $_ and return 1 } 0 }
512 sub basename { local $_ = shift; s|/*\s*$||; s|.*/||; $_ }
513 sub output { my $f = shift; open(my $F, ">$f") or die "output in file $f failed: $!\n"; print $F $_ foreach @_; 1 }
514 sub trim { $_[0] =~ s/^\s+//; $_[0] =~ s/\s+$//; return $_[0]; }
516 my $dur = gettimeofday() - $time;
517 # print STDERR $_[0] . " in " . $dur . "\n";
518 $time = gettimeofday();