Changeset 870

Show
Ignore:
Timestamp:
12/03/08 04:09:23 (5 weeks ago)
Author:
xi
Message:

Ported .pivot(); extracted transformers.py and descriptors.py from producers.py.

Location:
branches/forest-queries
Files:
2 added
3 modified

Legend:

Unmodified
Added
Removed
  • branches/forest-queries/htsql/commands/__init__.py

    r421 r870  
    2020from basics import * 
    2121from producers import * 
     22from transformers import * 
     23from descriptors import * 
    2224from formatters import * 
    2325 
  • branches/forest-queries/htsql/commands/producers.py

    r866 r870  
    652652 
    653653 
    654 class Transformer(Command): 
    655     """The base class for transformer commands.""" 
    656  
    657     supported_input = [PRODUCER] 
    658     supported_output = [PRODUCER, RENDERER] 
    659  
    660     def produce(self, connection=None, data=None): 
    661         """Perform the command.""" 
    662         if connection is None: 
    663             connection = self.app.make_connection() 
    664             try: 
    665                 product = self.execute(connection, data) 
    666             except: 
    667                 connection.rollback() 
    668                 raise 
    669             else: 
    670                 connection.commit() 
    671         else: 
    672             product = self.execute(connection, data) 
    673         return product 
    674  
    675     def execute(self, connection, data=None): 
    676         """Execute the previous command and transform the output.""" 
    677         product = self.input.execute(connection, data) 
    678         if not product: 
    679             raise errors.InvalidQuery("no output data is generated", self.mark) 
    680         return self.convert(product) 
    681  
    682     def convert(self, product): 
    683         assert False, "abstract method" 
    684  
    685     def render(self, environ): 
    686         """Generate HTTP output.""" 
    687         container = self.app.make_commands() 
    688         command = container.by_http_accept(environ, self) 
    689         return command.render(environ) 
    690  
    691  
    692 class FlattenGenerator(object): 
    693  
    694     def __init__(self, generator, children, widths): 
    695         self.generator = generator 
    696         self.children = children 
    697         self.widths = widths 
    698         try: 
    699             label, values = generator.next() 
    700         except StopIteration: 
    701             label, values = None, None 
    702         self.current_label = label 
    703         self.current_values = values 
    704  
    705     def __iter__(self): 
    706         for row in self.flatten_children(None): 
    707             yield (0, row) 
    708  
    709     def flatten_children(self, label): 
    710         if not self.children[label]: 
    711             if label is not None: 
    712                 yield [] 
    713             return 
    714         children_rows = [] 
    715         for child_label in self.children[label]: 
    716             is_last = (child_label == self.children[label][-1]) 
    717             child_rows = self.flatten_segment(child_label, is_last) 
    718             children_rows.append(child_rows) 
    719         is_first = (label is not None) 
    720         while True: 
    721             is_empty = (not is_first) 
    722             is_first = False 
    723             row = [] 
    724             for child_rows in children_rows: 
    725                 child_row, is_child_empty = child_rows.next() 
    726                 row += child_row 
    727                 is_empty &= is_child_empty 
    728             if is_empty: 
    729                 return 
    730             yield row 
    731  
    732     def flatten_segment(self, label, is_last=False): 
    733         iterator = self.emit_segment(label) 
    734         if not is_last: 
    735             iterator = list(iterator) 
    736         return self.loop_segment(label, iterator) 
    737  
    738     def emit_segment(self, label): 
    739         while self.current_label == label: 
    740             base = self.current_values 
    741             try: 
    742                 next_label, next_values = self.generator.next() 
    743             except StopIteration: 
    744                 next_label, next_values = None, None 
    745             self.current_label = next_label 
    746             self.current_values = next_values 
    747             for children in self.flatten_children(label): 
    748                 yield base+children 
    749  
    750     def loop_segment(self, label, iterator): 
    751         for row in iterator: 
    752             yield (row, False) 
    753         empty = [None]*self.widths[label] 
    754         while True: 
    755             yield (empty, True) 
    756  
    757  
    758 class Flatten(Transformer): 
    759     """The ``flatten()`` transformer.""" 
    760  
    761     name = ('htsql', 'flatten') 
    762  
    763     def convert(self, product): 
    764         if len(product.profile.segments) <= 1: 
    765             return product 
    766         new_generator = self.convert_generator(product.generator, 
    767                                                product.profile) 
    768         new_profile = self.convert_profile(product.profile) 
    769         new_product = Product(new_generator, new_profile, None) 
    770         return new_product 
    771  
    772     def convert_generator(self, generator, profile): 
    773         children = { None: [] } 
    774         for label, segment in enumerate(profile.segments): 
    775             parent_label = None 
    776             if segment.parent is not None: 
    777                 parent_label = profile.segments.index(segment.parent) 
    778             children[label] = [] 
    779             children[parent_label].append(label) 
    780         widths = {} 
    781         for label, segment in reversed(list(enumerate(profile.segments))): 
    782             parent_label = None 
    783             if segment.parent is not None: 
    784                 parent_label = profile.segments.index(segment.parent) 
    785             widths.setdefault(label, 0) 
    786             widths.setdefault(parent_label, 0) 
    787             widths[label] += len(segment.elements) 
    788             widths[parent_label] += widths[label] 
    789         generator = FlattenGenerator(generator, children, widths) 
    790         return iter(generator) 
    791  
    792     def convert_profile(self, profile): 
    793         root_segments = [segment for segment in profile.segments 
    794                                  if segment.parent is None] 
    795         is_forest = (len(root_segments) != 1) 
    796         if is_forest: 
    797             new_segment_title = descriptions.Title('') 
    798         else: 
    799             new_segment_title = root_segments[0].title 
    800         new_elements = [] 
    801         for segment in profile.segments: 
    802             parent_title = None 
    803             if segment.parent is not None or is_forest: 
    804                 parent_title = segment.title 
    805             for element in segment.elements: 
    806                 new_title = element.title 
    807                 if parent_title is not None: 
    808                     new_title = descriptions.CompositeTitle(parent_title, 
    809                                                             new_title) 
    810                 new_element = descriptions.Element(new_title, element.domain, 
    811                                                    element.entity) 
    812                 new_elements.append(new_element) 
    813         new_segment = descriptions.Segment(None, new_segment_title, 
    814                                            new_elements) 
    815         new_request = descriptions.Request(profile.authority, 
    816                                            profile.perspective, 
    817                                            profile.title, [new_segment]) 
    818         return new_request 
    819  
    820  
    821 class Pivot(Command): 
    822     """The ``pivot()`` transformer.""" 
    823  
    824     name = ('htsql', 'pivot') 
    825     supported_input = [PRODUCER] 
    826     supported_output = [PRODUCER, RENDERER] 
    827     declaration = [ 
    828             ('on', INT(), -2), 
    829             ('by', INT(), -1), 
    830     ] 
    831  
    832     def produce(self, connection=None, data=None): 
    833         """Perform the command.""" 
    834         if connection is None: 
    835             connection = self.app.make_connection() 
    836             try: 
    837                 product = self.execute(connection, data) 
    838             except: 
    839                 connection.rollback() 
    840                 raise 
    841             else: 
    842                 connection.commit() 
    843         else: 
    844             product = self.execute(connection, data) 
    845         return product 
    846  
    847     def execute(self, connection, data=None): 
    848         """Execute the request.""" 
    849         product = self.input.execute(connection, data) 
    850         if product is None: 
    851             raise errors.InvalidQuery("null input", self.mark) 
    852         if len(product.description.segments) != 1: 
    853             raise errors.InvalidQuery("pivot requires a single segment", 
    854                                       self.mark) 
    855         segment = product.description.segments[0] 
    856         on = self.on 
    857         if on > 0: 
    858             on -= 1 
    859         else: 
    860             on += len(segment.elements) 
    861         by = self.by 
    862         if by > 0: 
    863             by -= 1 
    864         else: 
    865             by += len(segment.elements) 
    866         if not (0 <= on < len(segment.elements) and 
    867                 0 <= by < len(segment.elements) and on != by): 
    868             raise errors.InvalidQuery("invalid arguments", self.mark) 
    869         return self.convert(product, on, by) 
    870  
    871     def convert(self, product, on, by): 
    872         rows = list(product) 
    873         pivot_values = set() 
    874         for row in rows: 
    875             pivot_values.add(row[on]) 
    876         pivot_values = list(pivot_values) 
    877         pivot_values.sort() 
    878         description = product.description 
    879         segment_description = description.segments[0] 
    880         new_elements = [] 
    881         domain = segment_description.elements[by].domain 
    882         for index, element in enumerate(segment_description.elements): 
    883             if index == on: 
    884                 for value in pivot_values: 
    885                     title = descriptions.Title(str(value)) 
    886                     element = descriptions.Element(title, domain) 
    887                     new_elements.append(element) 
    888             elif index != by: 
    889                 new_elements.append(element) 
    890         new_segment_description = descriptions.Segment( 
    891                 segment_description.title, new_elements) 
    892         new_description = descriptions.Request( 
    893                 description.title, [new_segment_description]) 
    894         key_to_position = {} 
    895         key_to_values = {} 
    896         for row in rows: 
    897             key = tuple(value for index, value in enumerate(row) 
    898                               if index not in (on, by)) 
    899             if key not in key_to_values: 
    900                 key_to_position[key] = len(key_to_values) 
    901                 key_to_values[key] = [None]*len(pivot_values) 
    902             values = key_to_values[key] 
    903             value_index = pivot_values.index(row[on]) 
    904             if row[by] is not None: 
    905                 if values[value_index] is not None  \ 
    906                 and values[value_index] != row[by]: 
    907                     raise errors.InvalidQuery("duplicate row", self.mark) 
    908             values[value_index] = row[by] 
    909         new_rows = [] 
    910         for key in sorted(key_to_values, key=(lambda k: key_to_position[k])): 
    911             values = key_to_values[key] 
    912             key = list(key) 
    913             key[on:on] = values 
    914             new_rows.append((0, key)) 
    915         return Product(iter(new_rows), new_description, None) 
    916  
    917     def render(self, environ): 
    918         """Generate HTTP output.""" 
    919         container = self.app.make_commands() 
    920         command = container.by_http_accept(environ, self) 
    921         return command.render(environ) 
    922  
    923  
    924654class Describe(Command): 
    925655 
  • branches/forest-queries/test/expect.yaml

    r867 r870  
    84068406        - [Content-Type, text/plain; charset=UTF-8] 
    84078407        body: |2 
    8408            | organization                             | 
    8409           -+------------------------------------------+- 
    8410            | id()        | None | False    | True     | 
    8411           -+-------------+------+----------+----------+- 
    8412            | acorn       |      |          |          | 
    8413            | lake-apts   |      | lakeside |          | 
    8414            | lake-carmen |      |          | lakeside | 
    8415            | lakeside    |      |          |          | 
    8416            | meyers      |      |          |          | 
    8417            | meyers_elec |      |          | meyers   | 
    8418            | smith       |      |          |          | 
    8419  
    8420             ----- 
    8421             /organization{id(),is_active,division_of}/ 
    8422             (7 rows) 
     8408           | organization                         | 
     8409          -+--------------------------------------+- 
     8410           | id()        |  | false    | true     | 
     8411          -+-------------+--+----------+----------+- 
     8412           | acorn       |  |          |          | 
     8413           | lake-apts   |  | lakeside |          | 
     8414           | lake-carmen |  |          | lakeside | 
     8415           | lakeside    |  |          |          | 
     8416           | meyers      |  |          |          | 
     8417           | meyers_elec |  |          | meyers   | 
     8418           | smith       |  |          |          | 
     8419                                           (7 rows) 
     8420 
     8421           ----- 
     8422           /organization{id(),is_active,division_of}/ 
    84238423      - uri: /organization{id(),is_active,division_of}/select().pivot(on='-2',by='-1') 
    84248424        status: 200 OK 
     
    84268426        - [Content-Type, text/plain; charset=UTF-8] 
    84278427        body: |2 
    8428            | organization                             | 
    8429           -+------------------------------------------+- 
    8430            | id()        | None | False    | True     | 
    8431           -+-------------+------+----------+----------+- 
    8432            | acorn       |      |          |          | 
    8433            | lake-apts   |      | lakeside |          | 
    8434            | lake-carmen |      |          | lakeside | 
    8435            | lakeside    |      |          |          | 
    8436            | meyers      |      |          |          | 
    8437            | meyers_elec |      |          | meyers   | 
    8438            | smith       |      |          |          | 
    8439  
    8440             ----- 
    8441             /organization{id(),is_active,division_of}/ 
    8442             (7 rows) 
     8428           | organization                         | 
     8429          -+--------------------------------------+- 
     8430           | id()        |  | false    | true     | 
     8431          -+-------------+--+----------+----------+- 
     8432           | acorn       |  |          |          | 
     8433           | lake-apts   |  | lakeside |          | 
     8434           | lake-carmen |  |          | lakeside | 
     8435           | lakeside    |  |          |          | 
     8436           | meyers      |  |          |          | 
     8437           | meyers_elec |  |          | meyers   | 
     8438           | smith       |  |          |          | 
     8439                                           (7 rows) 
     8440 
     8441           ----- 
     8442           /organization{id(),is_active,division_of}/ 
    84438443      - uri: /organization{id(),is_active,division_of}/select().pivot() 
    84448444        status: 200 OK 
     
    84468446        - [Content-Type, text/plain; charset=UTF-8] 
    84478447        body: |2 
    8448            | organization                             | 
    8449           -+------------------------------------------+- 
    8450            | id()        | None | False    | True     | 
    8451           -+-------------+------+----------+----------+- 
    8452            | acorn       |      |          |          | 
    8453            | lake-apts   |      | lakeside |          | 
    8454            | lake-carmen |      |          | lakeside | 
    8455            | lakeside    |      |          |          | 
    8456            | meyers      |      |          |          | 
    8457            | meyers_elec |      |          | meyers   | 
    8458            | smith       |      |          |          | 
    8459  
    8460             ----- 
    8461             /organization{id(),is_active,division_of}/ 
    8462             (7 rows) 
     8448           | organization                         | 
     8449          -+--------------------------------------+- 
     8450           | id()        |  | false    | true     | 
     8451          -+-------------+--+----------+----------+- 
     8452           | acorn       |  |          |          | 
     8453           | lake-apts   |  | lakeside |          | 
     8454           | lake-carmen |  |          | lakeside | 
     8455           | lakeside    |  |          |          | 
     8456           | meyers      |  |          |          | 
     8457           | meyers_elec |  |          | meyers   | 
     8458           | smith       |  |          |          | 
     8459                                           (7 rows) 
     8460 
     8461           ----- 
     8462           /organization{id(),is_active,division_of}/ 
    84638463      - uri: /project{client^,status^,count()} 
    84648464        status: 200 OK 
     
    84938493           | smith       | 1         | 2         |             |         | 
    84948494           |             |           |           | 1           |         | 
    8495  
    8496             ----- 
    8497             /project{client^,status^,count()}/ 
    8498             (4 rows) 
     8495                                                                  (4 rows) 
     8496 
     8497           ----- 
     8498           /project{client^,status^,count()}/ 
    84998499      - uri: /project{client^,status^,count()}/select().pivot(on='-2',by='-1') 
    85008500        status: 200 OK 
     
    85108510           | smith       | 1         | 2         |             |         | 
    85118511           |             |           |           | 1           |         | 
    8512  
    8513             ----- 
    8514             /project{client^,status^,count()}/ 
    8515             (4 rows) 
     8512                                                                  (4 rows) 
     8513 
     8514           ----- 
     8515           /project{client^,status^,count()}/ 
    85168516      - uri: /project{client^,status^,count()}/select().pivot() 
    85178517        status: 200 OK 
     
    85278527           | smith       | 1         | 2         |             |         | 
    85288528           |             |           |           | 1           |         | 
    8529  
    8530             ----- 
    8531             /project{client^,status^,count()}/ 
    8532             (4 rows) 
     8529                                                                  (4 rows) 
     8530 
     8531           ----- 
     8532           /project{client^,status^,count()}/ 
    85338533    - id: /describe() 
    85348534      tests: