/[cvs]/nfo/perl/libs/Tangram/Relational/Engine.pm
ViewVC logotype

Contents of /nfo/perl/libs/Tangram/Relational/Engine.pm

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1.2 - (show annotations)
Fri Nov 15 12:37:37 2002 UTC (21 years, 7 months ago) by joko
Branch: MAIN
CVS Tags: HEAD
Changes since 1.1: +8 -3 lines
+ Engine.pm now can create proper "CREATE TABLE" statements when specifying the table type (e.g. 'MyISAM', 'InnoDB', 'BerkeleyDB')

1 # (c) Sound Object Logic 2000-2001
2
3 use strict;
4
5 package Tangram::Relational::TableSet;
6
7 use constant TABLES => 0;
8 use constant SORTED_TABLES => 1;
9 use constant KEY => 2;
10
11 sub new
12 {
13 my $class = shift;
14 my %seen;
15 my @tables = grep { !$seen{$_}++ } @_;
16 my @sorted_tables = sort @tables;
17
18 return bless [ \@tables, \@sorted_tables, "@sorted_tables" ], $class;
19 }
20
21 sub key
22 {
23 return shift->[KEY];
24 }
25
26 sub tables
27 {
28 @{ shift->[TABLES] }
29 }
30
31 sub is_improper_superset
32 {
33 my ($self, $other) = @_;
34 my %other_tables = map { $_ => 1 } $other->tables();
35
36 for my $table ($self->tables()) {
37 delete $other_tables{$table};
38 return 1 if keys(%other_tables) == 0;
39 }
40
41 return 0;
42 }
43
44 package Tangram::Relational::Engine;
45
46 sub new
47 {
48 my ($class, $schema, %opts) = @_;
49
50 my $heterogeneity = { };
51 my $engine = bless { SCHEMA => $schema, HETEROGENEITY => $heterogeneity }, $class;
52
53 if ($opts{layout1}) {
54 $engine->{layout1} = 1;
55 $engine->{TYPE_COL} = $schema->{sql}{class_col} || 'classId';
56 } else {
57 $engine->{TYPE_COL} = $schema->{sql}{class_col} || 'type';
58 }
59
60 for my $class ($schema->all_classes) {
61 $engine->{ROOT_TABLES}{$class->{table}} = 1
62 if $class->is_root();
63 }
64
65 for my $class ($schema->all_classes) {
66
67 $engine->{ROOT_TABLES}{$class->{table}} = 1
68 if $class->is_root();
69
70 next if $class->{abstract};
71
72 my $table_set = $engine->get_table_set($class);
73 my $key = $table_set->key();
74
75 for my $other ($schema->all_classes) {
76 ++$heterogeneity->{$key} if my $ss = $engine->get_table_set($other)->is_improper_superset($table_set);
77 my $other_key = $engine->get_table_set($other)->key;
78 }
79 }
80
81 # use Data::Dumper; print Dumper $heterogeneity;
82
83 return $engine;
84 }
85
86 sub get_table_set
87 {
88 my ($self, $class) = @_;
89
90 return $self->{CLASSES}{$class->{name}}{table_set} ||= do {
91
92 my @table;
93
94 if ($self->{ROOT_TABLES}{$class->{table}}) {
95 push @table, $class->{table};
96 } else {
97 my $context = { layout1 => $self->{layout1} };
98
99 for my $field ($class->direct_fields()) {
100 if ($field->get_export_cols($context)) {
101 push @table, $class->{table};
102 last;
103 }
104 }
105 }
106
107 Tangram::Relational::TableSet
108 ->new((map { $self->get_table_set($_)->tables } $class->direct_bases()), @table );
109 };
110 }
111
112 sub get_parts
113 {
114 my ($self, $class) = @_;
115
116 @{ $self->{CLASSES}{$class->{name}}{PARTS} ||= do {
117 my %seen;
118 [ grep { !$seen{ $_->{name} }++ }
119 (map { $self->get_parts($_) } $class->direct_bases()),
120 $class
121 ]
122 } }
123 }
124
125 sub get_save_cache
126 {
127 my ($self, $class) = @_;
128
129 return $self->{CLASSES}{$class}{SAVE} ||= do {
130
131 my $schema = $self->{SCHEMA};
132 my $id_col = $schema->{sql}{id_col};
133 my $type_col = $self->{TYPE_COL};
134
135 my (%tables, @tables);
136 my (@export_sources, @export_closures);
137
138 my $context = { layout1 => $self->{layout1} };
139
140 my $field_index = 2;
141
142 for my $part ($self->get_parts($class)) {
143 my $table_name = $part->{table};
144
145 $context->{class} = $part;
146
147 my $table = $tables{$table_name} ||= do { push @tables, my $table = [ $table_name, [], [] ]; $table };
148
149 for my $field ($part->direct_fields()) {
150
151 my $exporter = $field->get_exporter($context)
152 or next;
153
154 if (ref $exporter) {
155 push @export_closures, $exporter;
156 push @export_sources, 'shift(@closures)->($obj, $context)';
157 } else {
158 push @export_sources, $exporter;
159 }
160
161 my @export_cols = $field->get_export_cols($context);
162 push @{ $table->[1] }, @export_cols;
163 push @{ $table->[2] }, $field_index..($field_index + $#export_cols);
164 $field_index += @export_cols;
165 }
166 }
167
168 my $export_source = join ",\n", @export_sources;
169 my $copy_closures = @export_closures ? ' my @closures = @export_closures;' : '';
170
171 # $Tangram::TRACE = \*STDOUT;
172
173 $export_source = "sub { my (\$obj, \$context) = \@_;$copy_closures\n$export_source }";
174
175 print $Tangram::TRACE "Compiling exporter for $class->{name}...\n$export_source\n"
176 if $Tangram::TRACE;
177
178 # use Data::Dumper; print Dumper \@cols;
179 my $exporter = eval $export_source or die;
180
181 my (@inserts, @updates, @insert_fields, @update_fields);
182
183 for my $table (@tables) {
184 my ($table_name, $cols, $fields) = @$table;
185 my @meta = ( $id_col );
186 my @meta_fields = ( 0 );
187
188 if ($self->{ROOT_TABLES}{$table_name}) {
189 push @meta, $type_col;
190 push @meta_fields, 1;
191 }
192
193 next unless @meta > 1 || @$cols;
194
195 push @inserts, sprintf('INSERT INTO %s (%s) VALUES (%s)',
196 $table_name,
197 join(', ', @meta, @$cols),
198 join(', ', ('?') x (@meta + @$cols)));
199 push @insert_fields, [ @meta_fields, @$fields ];
200
201 if (@$cols) {
202 push @updates, sprintf('UPDATE %s SET %s WHERE %s = ?',
203 $table_name,
204 join(', ', map { "$_ = ?" } @$cols),
205 $id_col);
206 push @update_fields, [ @$fields, 0 ];
207 }
208 }
209
210 {
211 EXPORTER => $exporter,
212 INSERT_FIELDS => \@insert_fields, INSERTS => \@inserts,
213 UPDATE_FIELDS => \@update_fields, UPDATES => \@updates,
214 }
215 };
216 }
217
218 sub get_instance_select
219 {
220 my ($self, $class) = @_;
221
222 return $self->{CLASSES}{$class}{INSTANCE_SELECT} ||= do {
223 my $schema = $self->{SCHEMA};
224 my $id_col = $schema->{sql}{id_col};
225 my $context = { engine => $self, schema => $schema, layout1 => $self->{layout1} };
226 my @cols;
227
228 for my $part ($self->get_parts($class)) {
229 my $table = $part->{table};
230 $context->{class} = $part;
231 push @cols, map { "$table.$_" } map { $_->get_import_cols($context) } $part->direct_fields()
232 }
233
234 my ($first_table, @other_tables) = $self->get_table_set($class)->tables();
235
236 sprintf("SELECT %s FROM %s WHERE %s",
237 join(', ', @cols),
238 join(', ', $first_table, @other_tables),
239 join(' AND ', "$first_table.$id_col = ?", map { "$first_table.$id_col = $_.$id_col" } @other_tables));
240 };
241 }
242
243 sub get_polymorphic_select
244 {
245 my ($self, $class, $storage) = @_;
246
247 my $selects = $self->{CLASSES}{$class}{POLYMORPHIC_SELECT} ||= do {
248 my $schema = $self->{SCHEMA};
249 my $id_col = $schema->{sql}{id_col};
250 my $type_col = $self->{TYPE_COL};
251 my $context = { engine => $self, schema => $schema, layout1 => $self->{layout1} };
252
253 my $table_set = $self->get_table_set($class);
254 my %base_tables = do { my $ph = 0; map { $_ => $ph++ } $table_set->tables() };
255
256 my %partition;
257
258 $class->for_conforming(sub {
259 my $class = shift;
260 push @{ $partition{ $self->get_table_set($class)->key } }, $class
261 unless $class->{abstract};
262 } );
263
264 my @selects;
265
266 for my $table_set_key (keys %partition) {
267
268 my $mates = $partition{$table_set_key};
269
270 my %slice;
271 my %col_index;
272 my $col_mark = 0;
273 my (@cols, @expand);
274
275 my @tables = $self->get_table_set($mates->[0])->tables();
276
277 my $root_table = $tables[0];
278 push @cols, qualify($id_col, $root_table, \%base_tables, \@expand);
279 push @cols, qualify($type_col, $root_table, \%base_tables, \@expand);
280
281 my %used;
282 $used{$root_table} += 2;
283
284 for my $class (@$mates) {
285 my @slice;
286
287 for my $part ($self->get_parts($class)) {
288 my $table = $part->{table};
289 $context->{class} = $part;
290
291 for my $field ($part->direct_fields()) {
292 my @import_cols = $field->get_import_cols($context);
293 $used{$table} += @import_cols;
294
295 for my $col (@import_cols) {
296 my $qualified_col = "$table.$col";
297 unless (exists $col_index{$qualified_col}) {
298 push @cols, qualify($col, $table, \%base_tables, \@expand);
299 $col_index{$qualified_col} = $col_mark++;
300 }
301
302 push @slice, $col_index{$qualified_col};
303 }
304 }
305 }
306
307 $slice{ $storage->{class2id}{$class->{name}} || $class->{id} } = \@slice; # should be $class->{id} (compat)
308 }
309
310 my @from;
311
312 for my $table (@tables) {
313 next unless $used{$table};
314 if (exists $base_tables{$table}) {
315 push @expand, $base_tables{$table};
316 push @from, "$table t%d";
317 } else {
318 push @from, $table;
319 }
320 }
321
322 my @where = map {
323 qualify($id_col, $root_table, \%base_tables, \@expand) . ' = ' . qualify($id_col, $_, \%base_tables, \@expand)
324 } grep { $used{$_} } @tables[1..$#tables];
325
326 unless (@$mates == $self->{HETEROGENEITY}{$table_set_key}) {
327 push @where, sprintf "%s IN (%s)", qualify($type_col, $root_table, \%base_tables, \@expand),
328 join ', ', map {
329 $storage->{class2id}{$_->{name}} or $_->{id} # try $storage first for compatibility with layout1
330 } @$mates
331 }
332
333 push @selects, Tangram::Relational::PolySelectTemplate->new(\@expand, \@cols, \@from, \@where, \%slice);
334 }
335
336 \@selects;
337 };
338
339 return @$selects;
340 }
341
342 sub qualify
343 {
344 my ($col, $table, $ph, $expand) = @_;
345
346 if (exists $ph->{$table}) {
347 push @$expand, $ph->{$table};
348 return "t%d.$col";
349 } else {
350 return "$table.$col";
351 }
352 }
353
354 sub get_import_cache
355 {
356 my ($self, $class) = @_;
357
358 return $self->{CLASSES}{$class}{IMPORTER} ||=
359 do {
360 my $schema = $self->{SCHEMA};
361
362 my $context = { schema => $schema, layout1 => $self->{layout1} };
363
364 my (@import_sources, @import_closures);
365
366 for my $part ($self->get_parts($class)) {
367 my $table_name = $part->{table};
368
369 $context->{class} = $part;
370
371 for my $field ($part->direct_fields) {
372
373 my $importer = $field->get_importer($context)
374 or next;
375
376 if (ref $importer) {
377 push @import_closures, $importer;
378 push @import_sources, 'shift(@closures)->($obj, $row, $context)';
379 } else {
380 push @import_sources, $importer;
381 }
382 }
383 }
384
385 my $import_source = join ";\n", @import_sources;
386 my $copy_closures = @import_closures ? ' my @closures = @import_closures;' : '';
387
388 # $Tangram::TRACE = \*STDOUT;
389
390 $import_source = "sub { my (\$obj, \$row, \$context) = \@_;$copy_closures\n$import_source }";
391
392 print $Tangram::TRACE "Compiling importer for $class->{name}...\n$import_source\n"
393 if $Tangram::TRACE;
394
395 # use Data::Dumper; print Dumper \@cols;
396 eval $import_source or die;
397 };
398 }
399
400 sub get_deletes
401 {
402 my ($self, $class) = @_;
403
404 return $self->{CLASSES}{$class}{DELETES} ||= do {
405 my $id_col = $self->{SCHEMA}{sql}{id_col};
406 [ map { "DELETE FROM $_ WHERE $id_col = ?" } $self->get_table_set($class)->tables() ]
407 };
408 }
409
410 sub deploy
411 {
412 my ($self, $out) = @_;
413 $self->relational_schema()->deploy($out);
414 }
415
416 sub retreat
417 {
418 my ($self, $out) = @_;
419 $self->relational_schema()->retreat($out);
420 }
421
422 sub get_deploy_info
423 {
424 my ($self) = @_;
425 return { LAYOUT => 2, ENGINE => ref($self), ENGINE_LAYOUT => 1 };
426 }
427
428 sub relational_schema
429 {
430 my ($self) = @_;
431
432 my $schema = $self->{SCHEMA};
433 my $classes = $schema->{classes};
434 my $tables = {};
435
436 foreach my $class (keys %{$schema->{classes}}) {
437
438 my $classdef = $classes->{$class};
439 my $tabledef = $tables->{ $classdef->{table} } ||= {};
440 my $cols = $tabledef->{COLS} ||= {};
441
442 $cols->{ $schema->{sql}{id_col} } = $schema->{sql}{id};
443
444 $cols->{ $schema->{sql}{class_col} || 'type' } = $schema->{sql}{cid} if $self->{ROOT_TABLES}{$classdef->{table}};
445
446 $tables->{$class}->{SQL} = $classdef->{sql} if $classdef->{sql};
447
448 foreach my $typetag (keys %{$classdef->{members}})
449 {
450 my $members = $classdef->{members}{$typetag};
451 my $type = $schema->{types}{$typetag};
452
453 $type->coldefs($tabledef->{COLS}, $members, $schema, $class, $tables);
454 }
455 }
456
457 delete @$tables{ grep { 1 == keys %{ $tables->{$_}{COLS} } } keys %$tables };
458
459 return bless [ $tables, $self ], 'Tangram::RelationalSchema';
460 }
461
462 sub Tangram::Scalar::_coldefs
463 {
464 my ($self, $cols, $members, $sql, $schema) = @_;
465
466 for my $def (values %$members)
467 {
468 $cols->{ $def->{col} } = $def->{sql} || "$sql $schema->{sql}{default_null}";
469 }
470 }
471 sub Tangram::Integer::coldefs
472 {
473 my ($self, $cols, $members, $schema) = @_;
474 $self->_coldefs($cols, $members, 'INT', $schema);
475 }
476
477 sub Tangram::Real::coldefs
478 {
479 my ($self, $cols, $members, $schema) = @_;
480 $self->_coldefs($cols, $members, 'REAL', $schema);
481 }
482
483 # sub Tangram::Ref::coldefs
484 # {
485 # my ($self, $cols, $members, $schema) = @_;
486
487 # for my $def (values %$members)
488 # {
489 # $cols->{ $def->{col} } = !exists($def->{null}) || $def->{null}
490 # ? "$schema->{sql}{id} $schema->{sql}{default_null}"
491 # : $schema->{sql}{id};
492 # }
493 # }
494
495 sub Tangram::String::coldefs
496 {
497 my ($self, $cols, $members, $schema) = @_;
498 $self->_coldefs($cols, $members, 'VARCHAR(255)', $schema);
499 }
500
501 sub Tangram::Set::coldefs
502 {
503 my ($self, $cols, $members, $schema, $class, $tables) = @_;
504
505 foreach my $member (values %$members)
506 {
507 $tables->{ $member->{table} }{COLS} =
508 {
509 $member->{coll} => $schema->{sql}{id},
510 $member->{item} => $schema->{sql}{id},
511 };
512 }
513 }
514
515 sub Tangram::IntrSet::coldefs
516 {
517 my ($self, $cols, $members, $schema, $class, $tables) = @_;
518
519 foreach my $member (values %$members)
520 {
521 my $table = $tables->{ $schema->{classes}{$member->{class}}{table} } ||= {};
522 $table->{COLS}{$member->{coll}} = "$schema->{sql}{id} $schema->{sql}{default_null}";
523 }
524 }
525
526 sub Tangram::Array::coldefs
527 {
528 my ($self, $cols, $members, $schema, $class, $tables) = @_;
529
530 foreach my $member (values %$members)
531 {
532 $tables->{ $member->{table} }{COLS} =
533 {
534 $member->{coll} => $schema->{sql}{id},
535 $member->{item} => $schema->{sql}{id},
536 $member->{slot} => "INT $schema->{sql}{default_null}"
537 };
538 }
539 }
540
541 sub Tangram::Hash::coldefs
542 {
543 my ($self, $cols, $members, $schema, $class, $tables) = @_;
544
545 foreach my $member (values %$members)
546 {
547 $tables->{ $member->{table} }{COLS} =
548 {
549 $member->{coll} => $schema->{sql}{id},
550 $member->{item} => $schema->{sql}{id},
551 $member->{slot} => "VARCHAR(255) $schema->{sql}{default_null}"
552 };
553 }
554 }
555
556 sub Tangram::IntrArray::coldefs
557 {
558 my ($self, $cols, $members, $schema, $class, $tables) = @_;
559
560 foreach my $member (values %$members)
561 {
562 my $table = $tables->{ $schema->{classes}{$member->{class}}{table} } ||= {};
563 $table->{COLS}{$member->{coll}} = "$schema->{sql}{id} $schema->{sql}{default_null}";
564 $table->{COLS}{$member->{slot}} = "INT $schema->{sql}{default_null}";
565 }
566 }
567
568 sub Tangram::HashRef::coldefs
569 {
570 #later
571 }
572
573 sub Tangram::BackRef::coldefs
574 {
575 return ();
576 }
577
578 package Tangram::RelationalSchema;
579
580 sub _deploy_do
581 {
582 my $output = shift;
583
584 return ref($output) && eval { $output->isa('DBI::db') }
585 ? sub { print $Tangram::TRACE @_, "\n" if $Tangram::TRACE;
586 $output->do( join '', @_ ); }
587 : sub { print $output @_, ";\n\n" };
588 }
589
590 sub deploy
591 {
592 my ($self, $output) = @_;
593 my ($tables, $engine) = @$self;
594 my $schema = $engine->{SCHEMA};
595
596 $output ||= \*STDOUT;
597
598 my $do = _deploy_do($output);
599
600 foreach my $table (sort keys %$tables)
601 {
602 my $def = $tables->{$table};
603 my $cols = $def->{COLS};
604 my $sql = $def->{SQL};
605
606 my @base_cols;
607 my $id_col = $schema->{sql}{id_col};
608 my $class_col = $schema->{sql}{class_col} || 'type';
609 my $table_type = $sql->{table_type} || $schema->{sql}{table_type};
610
611 push @base_cols, "$id_col $schema->{sql}{id} NOT NULL,\n PRIMARY KEY( id )" if exists $cols->{$id_col};
612 push @base_cols, "$class_col $schema->{sql}{cid} NOT NULL" if exists $cols->{$class_col};
613
614 delete @$cols{$id_col};
615 delete @$cols{$class_col};
616
617 $do->("CREATE TABLE $table\n(\n ",
618 join( ",\n ", @base_cols, map { "$_ $cols->{$_}" } keys %$cols ),
619 "\n)", ($table_type ? " TYPE=$table_type" : '') );
620 }
621
622 my $table_type = $schema->{sql}{table_type};
623 my $control_type = ($table_type ? " TYPE=$table_type" : '');
624 my $control = $schema->{control};
625
626 $do->( <<SQL );
627 CREATE TABLE $control
628 (
629 layout INTEGER NOT NULL,
630 engine VARCHAR(255),
631 engine_layout INTEGER,
632 mark INTEGER NOT NULL
633 )
634 $control_type
635 SQL
636
637 my $info = $engine->get_deploy_info();
638 my ($l) = split '\.', $Tangram::VERSION;
639
640 $do->("INSERT INTO $control (layout, engine, engine_layout, mark) VALUES ($info->{LAYOUT}, '$info->{ENGINE}', $info->{ENGINE_LAYOUT}, 0)");
641 }
642
643 sub retreat
644 {
645 my ($self, $output) = @_;
646 my ($tables, $engine) = @$self;
647 my $schema = $engine->{SCHEMA};
648
649 $output ||= \*STDOUT;
650
651 my $do = _deploy_do($output);
652
653 for my $table (sort keys %$tables, $schema->{control})
654 {
655 $do->( "DROP TABLE $table" );
656 }
657 }
658
659 sub classids
660 {
661 my ($self) = @_;
662 my ($tables, $schema) = @$self;
663 my $classes = $schema->{classes};
664 # use Data::Dumper;
665 return { map { $_ => $classes->{$_}{id} } keys %$classes };
666 }
667
668 package Tangram::Relational::PolySelectTemplate;
669
670 sub new
671 {
672 my $class = shift;
673 bless [ @_ ], $class;
674 }
675
676 sub instantiate
677 {
678 my ($self, $remote, $xcols, $xfrom, $xwhere) = @_;
679 my ($expand, $cols, $from, $where) = @$self;
680
681 $xcols ||= [];
682 $xfrom ||= [];
683
684 my @xwhere;
685
686 if (@$xwhere) {
687 $xwhere[0] = join ' AND ', @$xwhere;
688 $xwhere[0] =~ s[%][%%]g;
689 }
690
691 my @tables = $remote->table_ids();
692
693 my $select = sprintf "SELECT %s\n FROM %s", join(', ', @$cols, @$xcols), join(', ', @$from, @$xfrom);
694
695 $select = sprintf "%s\n WHERE %s", $select, join(' AND ', @$where, @xwhere)
696 if @$where || @$xwhere;
697
698 sprintf $select, map { $tables[$_] } @$expand;
699 }
700
701 sub extract
702 {
703 my ($self, $row) = @_;
704 my $id = shift @$row;
705 my $class_id = shift @$row;
706 my $slice = $self->[-1]{$class_id} or Carp::croak "unexpected class id '$class_id'";
707 my $state = [ @$row[ @$slice ] ];
708 splice @$row, 0, @{ $self->[1] } - 2;
709 return ($id, $class_id, $state);
710 }
711
712 1;

MailToCvsAdmin">MailToCvsAdmin
ViewVC Help
Powered by ViewVC 1.1.26 RSS 2.0 feed