Commits

Kirill Simonov committed 3769596

Added function `fiber()`, renamed `cross()` to `direct()`.

`parent.fiber(child, parent_expr, child_expr)` creates a custom join between
`parent` and `child` on condition `parent_expr = child_expr`.

  • Participants
  • Parent commits cc30973

Comments (0)

Files changed (3)

src/htsql/tr/fn/function.py

                        NumberDomain, IntegerDomain, DecimalDomain, FloatDomain,
                        DateDomain)
 from ..syntax import NumberSyntax, StringSyntax, IdentifierSyntax
-from ..binding import (LiteralBinding, SortBinding, FunctionBinding,
-                       EqualityBinding, TotalEqualityBinding,
+from ..binding import (LiteralBinding, SortBinding, SieveBinding,
+                       FunctionBinding, EqualityBinding, TotalEqualityBinding,
                        ConjunctionBinding, DisjunctionBinding, NegationBinding,
                        CastBinding, WrapperBinding, TitleBinding,
                        DirectionBinding)
         yield WrapperBinding(self.state.base, self.syntax)
 
 
-class CrossFunction(Function):
-
-    named('cross')
+class DirectFunction(Function):
+
+    named('direct')
 
     def __call__(self):
         if len(self.syntax.arguments) < 1:
         yield WrapperBinding(binding, self.syntax)
 
 
+class FiberFunction(Function):
+
+    named('fiber')
+
+    def __call__(self):
+        if len(self.syntax.arguments) != 3:
+            raise InvalidArgumentError("three arguments expected",
+                                       self.syntax.mark)
+        identifier = self.syntax.arguments[0]
+        if not isinstance(identifier, IdentifierSyntax):
+            raise InvalidArgumentError("an identifier expected",
+                                       identifier.mark)
+        binding = lookup(self.state.root, identifier)
+        if binding is None:
+            raise InvalidArgumentError("unknown identifier",
+                                       argument.mark)
+        binding = binding.clone(base=self.state.base)
+        parent = self.state.bind(self.syntax.arguments[1])
+        child = self.state.bind(self.syntax.arguments[2], base=binding)
+        domain = coerce(parent.domain, child.domain)
+        if domain is None:
+            raise InvalidArgumentError("incompatible types",
+                                       self.syntax.mark)
+        parent = CastBinding(parent, domain, parent.syntax)
+        child = CastBinding(child, domain, child.syntax)
+        condition = EqualityBinding(parent, child, self.syntax)
+        yield SieveBinding(binding, condition, self.syntax)
+
+
 class AsFunction(ProperFunction):
 
     named('as')

test/input/pgsql.yaml

     - uri: /department{code,school.code,
                        exists(school.program),school.exists(program)}
 
-  - title: Root, This and Join functions
+  - title: Root, This, Direct and Fiber functions
     tests:
     # Cross joins.
-    - uri: /{count(school)*count(department),count(school.cross(department))}
+    - uri: /{count(school)*count(department),count(school.direct(department))}
     # Custom joins.
     - uri: /{count(school.department),
-             count(school.cross(department)?school=root().school.code)}
+             count(school.direct(department)?school=root().school.code),
+             count(school.fiber(department,code,school.code))}
     # Lifting a unit to a dominating space.
     - uri: /{count(school),this(){count(school)}?true(),this(){count(school)}?false()}
     - uri: /school{code,name,this(){code}?name!~'art',

test/output/pgsql.yaml

          ----
          /department{code,school.code,exists(school.program),school.exists(program)}
          SELECT "department"."code", "school_1"."code", EXISTS((SELECT TRUE AS "!" FROM "ad"."program" AS "program" WHERE ("school_1"."code" = "program"."school"))), "school_2"."!" FROM "ad"."department" AS "department" LEFT OUTER JOIN "ad"."school" AS "school_1" ON (("department"."school" = "school_1"."code")) LEFT OUTER JOIN (SELECT EXISTS((SELECT TRUE AS "!" FROM "ad"."program" AS "program" WHERE ("school"."code" = "program"."school"))) AS "!", "school"."code" FROM "ad"."school" AS "school") AS "school_2" ON (("department"."school" = "school_2"."code")) ORDER BY 1 ASC
-  - id: root,-this-and-join-functions
+  - id: root,-this,-direct-and-fiber-functions
     tests:
-    - uri: /{count(school)*count(department),count(school.cross(department))}
+    - uri: /{count(school)*count(department),count(school.direct(department))}
       status: 200 OK
       headers:
       - [Content-Type, text/plain; charset=UTF-8]
       body: |2
-         |                                                                   |
-        -+-------------------------------------------------------------------+-
-         | count(school)*count(department) | count(school.cross(department)) |
-        -+---------------------------------+---------------------------------+-
-         |                             243 |                             243 |
-                                                                       (1 row)
+         |                                                                    |
+        -+--------------------------------------------------------------------+-
+         | count(school)*count(department) | count(school.direct(department)) |
+        -+---------------------------------+----------------------------------+-
+         |                             243 |                              243 |
+                                                                        (1 row)
 
          ----
-         /{count(school)*count(department),count(school.cross(department))}
+         /{count(school)*count(department),count(school.direct(department))}
          SELECT ("school"."!" * "department_1"."!"), "department_2"."!" FROM (SELECT COUNT(TRUE) AS "!" FROM "ad"."school" AS "school") AS "school" CROSS JOIN (SELECT COUNT(TRUE) AS "!" FROM "ad"."department" AS "department") AS "department_1" CROSS JOIN (SELECT COUNT(TRUE) AS "!" FROM "ad"."school" AS "school" CROSS JOIN "ad"."department" AS "department") AS "department_2"
-    - uri: /{count(school.department), count(school.cross(department)?school=root().school.code)}
+    - uri: /{count(school.department), count(school.direct(department)?school=root().school.code),
+        count(school.fiber(department,code,school.code))}
       status: 200 OK
       headers:
       - [Content-Type, text/plain; charset=UTF-8]
       body: |2
-         |                                                                                      |
-        -+--------------------------------------------------------------------------------------+-
-         | count(school.department) | count(school.cross(department)?school=root().school.code) |
-        -+--------------------------+-----------------------------------------------------------+-
-         |                       24 |                                                        24 |
-                                                                                          (1 row)
+         |                                                                                                                                          |
+        -+------------------------------------------------------------------------------------------------------------------------------------------+-
+         | count(school.department) | count(school.direct(department)?school=root().school.code) | count(school.fiber(department,code,school.code)) |
+        -+--------------------------+------------------------------------------------------------+--------------------------------------------------+-
+         |                       24 |                                                         24 |                                               24 |
+                                                                                                                                              (1 row)
 
          ----
-         /{count(school.department),count(school.cross(department)?school=root().school.code)}
-         SELECT "department_1"."!", "department_2"."!" FROM (SELECT COUNT(TRUE) AS "!" FROM "ad"."school" AS "school" INNER JOIN "ad"."department" AS "department" ON (("school"."code" = "department"."school"))) AS "department_1" CROSS JOIN (SELECT COUNT(TRUE) AS "!" FROM "ad"."school" AS "school" CROSS JOIN "ad"."department" AS "department" WHERE ("department"."school" = "school"."code")) AS "department_2"
+         /{count(school.department),count(school.direct(department)?school=root().school.code),count(school.fiber(department,code,school.code))}
+         SELECT "department_1"."!", "department_2"."!", "department_3"."!" FROM (SELECT COUNT(TRUE) AS "!" FROM "ad"."school" AS "school" INNER JOIN "ad"."department" AS "department" ON (("school"."code" = "department"."school"))) AS "department_1" CROSS JOIN (SELECT COUNT(TRUE) AS "!" FROM "ad"."school" AS "school" CROSS JOIN "ad"."department" AS "department" WHERE ("department"."school" = "school"."code")) AS "department_2" CROSS JOIN (SELECT COUNT(TRUE) AS "!" FROM "ad"."school" AS "school_1" CROSS JOIN "ad"."department" AS "department" LEFT OUTER JOIN "ad"."school" AS "school_2" ON (("department"."school" = "school_2"."code")) WHERE ("school_1"."code" = "school_2"."code")) AS "department_3"
     - uri: /{count(school),this(){count(school)}?true(),this(){count(school)}?false()}
       status: 200 OK
       headers:
          ----
          /(course?credits<3&department.school='ns'){department,number,title}
          SELECT "course"."department", "course"."number", "course"."title" FROM "ad"."course" AS "course" INNER JOIN "ad"."department" AS "department_2" ON (("course"."department" = "department_2"."code")) WHERE (("course"."credits" < 3) AND ("department_2"."school" = 'ns')) ORDER BY 1 ASC, 2 ASC
-    - uri: /course{department, number, title}?credits<3&department.school='ns'
-      status: 200 OK
-      headers:
-      - [Content-Type, text/plain; charset=UTF-8]
-      body: |2
-         | (course?credits<3&department.school='ns')            |
-        -+------------------------------------------------------+-
-         | department | number | title                          |
-        -+------------+--------+--------------------------------+-
-         | astro      |    142 | Solar System Lab               |
-         | astro      |    155 | Telescope Workshop             |
-         | chem       |    115 | Organic Chemistry Laboratory I |
-         | phys       |    388 | Experimental Physics I         |
-                                                         (4 rows)
-
-         ----
-         /course{department,number,title}?credits<3&department.school='ns'
-         SELECT "course"."department", "course"."number", "course"."title" FROM "ad"."course" AS "course" INNER JOIN (SELECT "department"."school", "department"."code" FROM "ad"."department" AS "department") AS "department_2" ON (("course"."department" = "department_2"."code")) WHERE ((CAST("course"."credits" AS INTEGER) < 3) AND (CAST("department_2"."school" AS TEXT) = 'ns')) ORDER BY 1 ASC, 2 ASC
-    - uri: /(course?credits<3&department.school='ns'){department, number, title}
-      status: 200 OK
-      headers:
-      - [Content-Type, text/plain; charset=UTF-8]
-      body: |2
-         | (course?credits<3&department.school='ns')            |
-        -+------------------------------------------------------+-
-         | department | number | title                          |
-        -+------------+--------+--------------------------------+-
-         | astro      |    142 | Solar System Lab               |
-         | astro      |    155 | Telescope Workshop             |
-         | chem       |    115 | Organic Chemistry Laboratory I |
-         | phys       |    388 | Experimental Physics I         |
-                                                         (4 rows)
-
-         ----
-         /(course?credits<3&department.school='ns'){department,number,title}
-         SELECT "course"."department", "course"."number", "course"."title" FROM "ad"."course" AS "course" INNER JOIN (SELECT "department"."school", "department"."code" FROM "ad"."department" AS "department") AS "department_2" ON (("course"."department" = "department_2"."code")) WHERE ((CAST("course"."credits" AS INTEGER) < 3) AND (CAST("department_2"."school" AS TEXT) = 'ns')) ORDER BY 1 ASC, 2 ASC
     - uri: /school/:json
       status: 200 OK
       headers:
         Statement Analysis\r\ncapmrk,Capital Markets,712,International Financial Markets\r\nacc,Accounting,100,Practical
         Bookkeeping\r\nacc,Accounting,200,Principles of Accounting I\r\ncapmrk,Capital
         Markets,808,Principles of Portfolio Management\r\n"
-    - uri: /course{department{code,name},number,title+}?department.school='bus'/:csv
-      status: 200 OK
-      headers:
-      - [Content-Type, text/csv; charset=UTF-8]
-      - [Content-Disposition, 'attachment; filename="(course{department{code,name},number,title+}?department.school=''bus'').csv"']
-      body: "department{code},department{name},number,title+\r\ncorpfi,Corporate Finance,234,Accounting
-        Information Systems\r\nacc,Accounting,527,Advanced Accounting\r\ncapmrk,Capital
-        Markets,756,Capital Risk Management\r\ncorpfi,Corporate Finance,601,Case Studies
-        in Corporate Finance\r\nacc,Accounting,606,Corporate Financial Law\r\ncorpfi,Corporate
-        Finance,404,Corporate Financial Management\r\nacc,Accounting,426,Corporate
-        Taxation\r\nacc,Accounting,315,Financial Accounting\r\ncapmrk,Capital Markets,818,Financial
-        Statement Analysis\r\ncapmrk,Capital Markets,712,International Financial Markets\r\nacc,Accounting,100,Practical
-        Bookkeeping\r\nacc,Accounting,200,Principles of Accounting I\r\ncapmrk,Capital
-        Markets,808,Principles of Portfolio Management\r\n"
     - uri: /course{department.name, credits}
       status: 200 OK
       headers: