├── .eggs ├── README.txt ├── lenses-0.3.0-py2.7.egg ├── singledispatch-3.4.0.3-py2.7.egg └── typing-3.6.2-py2.7.egg ├── .gitignore ├── Examples ├── example_tables.py └── examples.py ├── LICENSE.txt ├── LambdaQuery ├── __init__.py ├── do_notation.py ├── expr.py ├── functions.py ├── misc.py ├── query.py ├── reroute.py └── sql.py ├── README.md ├── docs ├── Makefile ├── _build │ ├── doctrees │ │ ├── advanced.doctree │ │ ├── environment.pickle │ │ ├── faq.doctree │ │ ├── index.doctree │ │ ├── motivation.doctree │ │ ├── properties.doctree │ │ ├── query.doctree │ │ └── start.doctree │ └── html │ │ ├── .buildinfo │ │ ├── _images │ │ └── schema.png │ │ ├── _sources │ │ ├── advanced.rst.txt │ │ ├── faq.rst.txt │ │ ├── index.rst.txt │ │ ├── motivation.rst.txt │ │ ├── properties.rst.txt │ │ ├── query.rst.txt │ │ └── start.rst.txt │ │ ├── _static │ │ ├── ajax-loader.gif │ │ ├── alabaster.css │ │ ├── basic.css │ │ ├── comment-bright.png │ │ ├── comment-close.png │ │ ├── comment.png │ │ ├── custom.css │ │ ├── darkmetal.png │ │ ├── doctools.js │ │ ├── down-pressed.png │ │ ├── down.png │ │ ├── file.png │ │ ├── headerbg.png │ │ ├── jquery-3.1.0.js │ │ ├── jquery.js │ │ ├── logo.png │ │ ├── metal.png │ │ ├── minus.png │ │ ├── navigation.png │ │ ├── plus.png │ │ ├── print.css │ │ ├── pygments.css │ │ ├── scrolls.css │ │ ├── searchtools.js │ │ ├── theme_extras.js │ │ ├── underscore-1.3.1.js │ │ ├── underscore.js │ │ ├── up-pressed.png │ │ ├── up.png │ │ ├── watermark.png │ │ ├── watermark_blur.png │ │ └── websupport.js │ │ ├── advanced.html │ │ ├── faq.html │ │ ├── genindex.html │ │ ├── index.html │ │ ├── motivation.html │ │ ├── objects.inv │ │ ├── properties.html │ │ ├── query.html │ │ ├── search.html │ │ ├── searchindex.js │ │ └── start.html ├── advanced.rst ├── conf.py ├── faq.rst ├── images │ ├── haskell_logo.svg │ └── schema.png ├── index.rst ├── make.bat ├── motivation.rst ├── properties.rst ├── query.rst └── start.rst ├── example_tables.py ├── examples.py ├── setup.cfg └── setup.py /.eggs/README.txt: -------------------------------------------------------------------------------- 1 | This directory contains eggs that were downloaded by setuptools to build, test, and run plug-ins. 2 | 3 | This directory caches those eggs to prevent repeated downloads. 4 | 5 | However, it is safe to delete this directory. 6 | 7 | -------------------------------------------------------------------------------- /.eggs/lenses-0.3.0-py2.7.egg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xnmp/lambdaquery/4ede46cb8518ff393898ffaf12cebe530dc90627/.eggs/lenses-0.3.0-py2.7.egg -------------------------------------------------------------------------------- /.eggs/singledispatch-3.4.0.3-py2.7.egg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xnmp/lambdaquery/4ede46cb8518ff393898ffaf12cebe530dc90627/.eggs/singledispatch-3.4.0.3-py2.7.egg -------------------------------------------------------------------------------- /.eggs/typing-3.6.2-py2.7.egg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xnmp/lambdaquery/4ede46cb8518ff393898ffaf12cebe530dc90627/.eggs/typing-3.6.2-py2.7.egg -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | Archive/* 2 | build/* 3 | dist/* 4 | 5 | Examples/__pycache__/* 6 | lambdaquery.egg-info/* 7 | LambdaQuery/__pycache__/* 8 | __pycache__/* 9 | tables.py 10 | test.py 11 | processing.py 12 | other_functions.py 13 | start.py 14 | -------------------------------------------------------------------------------- /Examples/example_tables.py: -------------------------------------------------------------------------------- 1 | from LambdaQuery import * 2 | from datetime import timedelta 3 | 4 | 5 | class School(Columns): 6 | table = Table("school", 'sc') 7 | def __init__(self): 8 | School.makeTable() 9 | self.baseExpr("school_code", 'code', primary=True) 10 | self.baseExpr("name", 'name') 11 | self.baseExpr("campus", "campus") 12 | 13 | class Department(Columns): 14 | table = Table("department", "dept") 15 | def __init__(self): 16 | Department.makeTable() 17 | self.baseExpr("dept_code", 'code', primary=True) 18 | self.baseExpr("school_code", 'sc_code', foreign=School) 19 | self.baseExpr("name", "name") 20 | 21 | class Program(Columns): 22 | table = Table("program", 'prog') 23 | def __init__(self): 24 | Program.makeTable() 25 | self.baseExpr("prog_code", 'code', primary=True) 26 | self.baseExpr("school_code", 'sc_code', foreign=School) 27 | self.baseExpr("title", "title") 28 | self.baseExpr("degree", 'degree') 29 | 30 | class Course(Columns): 31 | table = Table("course", "course") 32 | def __init__(self): 33 | Course.makeTable() 34 | self.baseExpr("course_code", 'no', primary=True) 35 | self.baseExpr("dept_code", 'dept_code', foreign=Department) 36 | self.baseExpr("description", "description") 37 | self.baseExpr("title", "title") 38 | self.baseExpr("credits", "credits") 39 | 40 | 41 | # ============================================================================ 42 | # instantiate all classes so that the primary and foreign keys are all defined 43 | # ============================================================================ 44 | 45 | for colclass in [subcls for subcls in Columns.__subclasses__()] \ 46 | + [subsubcls for subcls in Columns.__subclasses__() for subsubcls in subcls.__subclasses__()]: 47 | colclass() 48 | -------------------------------------------------------------------------------- /Examples/examples.py: -------------------------------------------------------------------------------- 1 | 2 | from example_tables import * 3 | 4 | # %% ^━━━━━━━━━━━━━━━━━━ ━━━━━━━━━━━━━━━━━━━━━━━━━━━^ 5 | 6 | ex1 = School.query() 7 | ex2 = School.query(lambda x: x.campus == 'south') 8 | ex3 = School.query().sort(lambda x: x.campus) 9 | ex4 = School.query().fmap(lambda x: x.name % x.campus) 10 | ex4 = School.query()['name','campus'] 11 | 12 | print(ex1.sql()) 13 | 14 | @do(Query) 15 | def ex4(): 16 | sc0 = yield School.query() 17 | returnM ( 18 | sc0.name, 19 | sc0.campus 20 | ) 21 | 22 | @do(Query) 23 | def ex5(): 24 | sc0 = yield School.query() 25 | returnM ( 26 | sc0.name, 27 | sc0.departments().count() 28 | ) 29 | 30 | @do(Query) 31 | def ex6(): 32 | dept0 = yield Department.query() 33 | returnM ( 34 | dept0.name, 35 | dept0.school.name, 36 | dept0.school.campus 37 | ) 38 | 39 | @injective() 40 | def num_dept(self): 41 | return self.departments().count() 42 | 43 | @do(Query) 44 | def ex7(): 45 | sc0 = yield School.query(lambda x: x.num_dept > 3) 46 | returnM ( 47 | sc0.code, 48 | sc0.num_dept 49 | ) 50 | 51 | ex7 = School.query(lambda x: x.num_dept > 3).fmap(lambda x: x.code % x.num_dept) 52 | ex7 = School.query(lambda x: x.num_dept > 3)[['code','num_dept']] 53 | 54 | @do(Query) 55 | def ex8(): 56 | cs0 = yield Course.query() 57 | returnM ( 58 | cs0.title, 59 | cs0.credits.avg_() 60 | ) 61 | 62 | @do(Query) 63 | def ex9(): 64 | sc0 = yield School.query() 65 | dept0 = yield sc0.departments() 66 | cs0 = yield dept0.courses() 67 | returnM ( 68 | sc0.name, 69 | dept0.name, 70 | cs0.title 71 | ) 72 | 73 | # %% ^━━━━━━━━━━━━━━━━━━ ━━━━━━━━━━━━━━━━━━━━━━━━━━━^ 74 | 75 | # # AGGREGATION 76 | 77 | # ex10 = Department.query().count() 78 | 79 | # ex11 = School.query().avg('num_dept') 80 | 81 | # @do(Query) 82 | # def ex12(): 83 | # dept0 = yield Department.query() 84 | # cs = dept0.courses() 85 | # returnM ( 86 | # dept0.name, 87 | # cs.count(), 88 | # cs.max('credits'), 89 | # cs.sum('credits'), 90 | # dept0.courses(lambda x: x.title.like_('Introduction%')).count() 91 | # ) 92 | 93 | # %% ^━━━━━━━━━━━━━━━━━━ ━━━━━━━━━━━━━━━━━━━━━━━━━━━^ 94 | 95 | 96 | @do(Query) 97 | def ex13(): 98 | sc0 = yield School.query() 99 | returnM ( 100 | sc0.name, 101 | sc0.programs()['degree'].count(), 102 | ) 103 | 104 | 105 | @do(Query) 106 | def ex14(): 107 | dept0 = yield Department.query() 108 | returnM ( 109 | dept0.name, 110 | dept0.school.campus 111 | ) 112 | 113 | 114 | @do(Query) 115 | def ex15(): 116 | dept0 = yield Department.query() 117 | returnM ( 118 | dept0.name, 119 | dept0.courses(lambda x: x.credits > 2).lj().count() 120 | ) 121 | 122 | 123 | @do(Query) 124 | def ex16(): 125 | sc0 = yield School.query() 126 | returnM ( 127 | sc0.campus, 128 | sc0.departments().lj().count() 129 | ) 130 | 131 | @Lifted 132 | def coursestart(course, start): 133 | return (course.no > start) & (course.no < start + 99) 134 | 135 | 136 | @do(Query) 137 | def ex17(): 138 | dept0 = yield Department.query() 139 | returnM ( 140 | dept0.name, 141 | *[dept0.courses(lambda x: x.coursestart(i*100)).count() for i in range(1,5)] 142 | ) 143 | 144 | @injective() 145 | def high_credit_count(self): 146 | return self.courses(lambda x: x.credits > 3).count() 147 | 148 | @do(Query) 149 | def ex18(): 150 | sc0 = yield School.query(lambda x: x.programs().exists()) 151 | returnM ( 152 | sc0.name, 153 | sc0.departments()['high_credit_count'].avg() 154 | ) 155 | 156 | 157 | @do(Query) 158 | def ex19(): 159 | sc0 = yield School.query(lambda x: x.programs(lambda y: y.title == x.name).count() > 5) 160 | returnM ( 161 | sc0.name, 162 | sc0.departments()['high_credit_count'].avg() 163 | ) 164 | 165 | print(ex19().sql()) 166 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright 2017 Chong Wang 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /LambdaQuery/__init__.py: -------------------------------------------------------------------------------- 1 | from LambdaQuery.functions import * 2 | from LambdaQuery.sql import * -------------------------------------------------------------------------------- /LambdaQuery/do_notation.py: -------------------------------------------------------------------------------- 1 | import types 2 | 3 | # gives do notation for python, with "yield" statements and enabling python code in between binds 4 | # take from http://www.valuedlessons.com/2008/01/monads-in-python-with-nice-syntax.html 5 | # just made a few changes to let mreturn handle a tuple, and to convert python 2 to 3 6 | # I actually have no idea how this works 7 | 8 | class Monad: 9 | def bind(self, func): 10 | raise NotImplementedError 11 | 12 | def __rshift__(self, bindee): 13 | return bindee(self) 14 | 15 | def __add__(self, bindee_without_arg): 16 | return self.bind(lambda _ : bindee_without_arg()) 17 | 18 | def make_decorator(func, *dec_args): 19 | def decorator(undecorated): 20 | def decorated(*args, **kargs): 21 | return func(undecorated, args, kargs, *dec_args) 22 | # print(func.__name__) 23 | decorated.__name__ = undecorated.__name__ 24 | return decorated 25 | 26 | decorator.__name__ = func.__name__ 27 | return decorator 28 | 29 | def make_decorator_with_args(func): 30 | def decorator_with_args(*dec_args): 31 | return make_decorator(func, *dec_args) 32 | return decorator_with_args 33 | 34 | decorator = make_decorator 35 | decorator_with_args = make_decorator_with_args 36 | 37 | @decorator_with_args 38 | def do(func, func_args, func_kargs, Monad): 39 | @handle_monadic_throws(Monad) 40 | def run_maybe_iterator(): 41 | itr = func(*func_args, **func_kargs) 42 | 43 | if isinstance(itr, types.GeneratorType): 44 | 45 | @handle_monadic_throws(Monad) 46 | def send(*vals): 47 | try: 48 | # here's the real magic 49 | monad = itr.send(*vals) 50 | return monad.bind(send) 51 | except StopIteration: 52 | return Monad.unit(None) 53 | 54 | return send(None) 55 | else: 56 | #not really a generator 57 | if itr is None: 58 | return Monad.unit(None) 59 | else: 60 | return itr 61 | run_maybe_iterator.__name__ = func.__name__ 62 | return run_maybe_iterator() 63 | 64 | @decorator_with_args 65 | def handle_monadic_throws(func, func_args, func_kargs, Monad): 66 | try: 67 | return func(*func_args, **func_kargs) 68 | # except Guard as cond: 69 | # return cond.cond.values().sum().asQuery() 70 | except MonadReturn as ret: 71 | return Monad.unit(*ret.values, **ret.kwvals) 72 | except Done as done: 73 | assert isinstance(done.monad, Monad) 74 | return done.monad 75 | 76 | class Guard(Exception): 77 | def __init__(self, cond): 78 | self.cond = cond 79 | Exception.__init__(self) 80 | 81 | class MonadReturn(Exception): 82 | def __init__(self, *values, **kwargs): 83 | self.values = values 84 | self.kwvals = kwargs 85 | Exception.__init__(self) 86 | 87 | class Done(Exception): 88 | def __init__(self, monad): 89 | self.monad = monad 90 | Exception.__init__(self, monad) 91 | 92 | def guard(cond): 93 | raise Guard(cond) 94 | # yield cond.values().sum().asQuery() 95 | 96 | def returnM(*vals, **kwargs): 97 | raise MonadReturn(*vals,**kwargs) 98 | 99 | def done(val): 100 | print("ghgjhgjh") 101 | raise Done(val) 102 | 103 | # def fid(val): 104 | # return val 105 | 106 | 107 | # example below for guac 108 | 109 | # class List(Monad): 110 | 111 | # def __init__(self, *args): 112 | # self.values = list(args) 113 | 114 | # @classmethod 115 | # def unit(cls, x): 116 | # return List(x) 117 | 118 | # def __add__(self, other): 119 | # self.values += other.values 120 | # return self 121 | 122 | # @classmethod 123 | # def empty(cls): 124 | # return List() 125 | 126 | # def bind(m, f): 127 | # result = List() 128 | # for elem in m: 129 | # result += f(elem) 130 | # return result 131 | 132 | # def __getitem__(self, n): 133 | # return self.values[n] 134 | 135 | # def __repr__(self): 136 | # return self.values.__repr__() 137 | 138 | # def append(self, newobj): 139 | # self.values.append(newobj) 140 | 141 | 142 | # @do(List) 143 | # def make_change(amount_still_owed, possible_coins): 144 | # change = List() 145 | 146 | 147 | # # Keep adding coins while we owe them money and there are still coins. 148 | # while amount_still_owed > 0 and possible_coins: 149 | 150 | # # "Nondeterministically" choose whether to give anther coin of this value. 151 | # # Aka, try both branches, and return both results. 152 | # give_min_coin = yield List(True, False) 153 | 154 | # if give_min_coin: 155 | # # Give coin 156 | # min_coin = possible_coins[0] 157 | # change.append(min_coin) 158 | # amount_still_owed -= min_coin 159 | # else: 160 | # # Never give this coin value again (in this branch!) 161 | # del possible_coins[0] 162 | 163 | # # Did we charge them the right amount? 164 | # yield guard(amount_still_owed == 0) 165 | 166 | # # Lift the result back into the monad. 167 | # returnM (change) 168 | 169 | # @do(List) 170 | # def guard(condition): 171 | # if condition: 172 | # yield List.unit(()) 173 | # else: 174 | # yield List.empty() 175 | 176 | # if __name__ == '__main__': 177 | # print(make_change(27, [1, 5, 10, 25])) 178 | -------------------------------------------------------------------------------- /LambdaQuery/functions.py: -------------------------------------------------------------------------------- 1 | from functools import wraps 2 | from LambdaQuery.query import * 3 | 4 | 5 | def asExpr(value): 6 | if isinstance(value, Columns): 7 | if len(value) > 1: 8 | raise TypeError(f"Can't convert Columns with more than one Expr to Expr: {value}") 9 | return value.asExpr() 10 | elif isinstance(value, ConstExpr.allowedTypes): 11 | return ConstExpr(value) 12 | elif isinstance(value, Expr): 13 | return value 14 | raise TypeError(f"Cannot convert to Expr: {value} of type {type(value)}") 15 | 16 | 17 | def labelResult(func, args): 18 | return func.__name__.strip('_') + '_' + args.bind(lambda x: x.keys() if type(x) is Columns 19 | else L(x.__class__.__name__.lower())).intersperse('_') 20 | 21 | 22 | def augment(func): 23 | # Columns -> Columns and Columns -> Query 24 | # all we want to do is lift func to something that carries through the joinconds and the groupbys 25 | # the complication is that we need it to addquery, or do we? 26 | @wraps(func) 27 | def mfunc(*args, **kwargs): 28 | 29 | res = func(*args, **kwargs) 30 | # if isinstance(res, Query): res = res.joinM() 31 | 32 | colargs = L(*args).filter(lambda x: isinstance(x, Columns)) 33 | oldgroupbys = colargs.bind(lambda x: x.groupbys) 34 | oldjc = colargs.fmap(lambda x: x.asQuery()).fold(lambda x, y: x | y) 35 | 36 | if isinstance(res, Query) and type(res.columns) is not Columns: 37 | for table in oldgroupbys.getTables(): 38 | table.derivatives += res.columns.getTables()[0] 39 | 40 | res.groupbys = oldgroupbys + res.groupbys 41 | # res.joincond @= colargs.fmap(lambda x: x.asQuery()).combine() 42 | 43 | if isinstance(res, Columns): 44 | res = addQuery(oldjc, res.asQuery(), addcols='right').asCols() 45 | if type(res) is Columns: res = res.label(labelResult(func, colargs)) 46 | else: 47 | res = addQuery(oldjc, res.asQuery(), addcols='right') 48 | # this breaks things 49 | # res.columns = res.columns.label(func.__name__) 50 | 51 | return res 52 | return mfunc 53 | 54 | 55 | def lift(func): 56 | """ 57 | Lifts Expr -> Expr to Columns -> Columns. "Applicative instance for Columns" 58 | """ 59 | @wraps(func) 60 | def colfunc(*args, **kwargs): 61 | 62 | res = Columns() 63 | colargs = L(*args).filter(lambda x: isinstance(x, Columns)) 64 | res[labelResult(func, colargs)] = func(*L(*args).fmap(asExpr), **kwargs) 65 | 66 | # replica of augment logic 67 | res.groupbys = colargs.bind(lambda x: x.groupbys) 68 | # we're NOT using addQuery here 69 | res.joincond &= colargs.fmap(lambda x: x.joincond).fold(lambda x, y: x & y, mzero=AndExpr()) 70 | # res.joincond @= colargs.fmap(lambda x: x.asQuery()).combine() 71 | # oldjc = colargs.fmap(lambda x: x.asQuery()).fold(lambda x, y: x @ y) 72 | # res = addQuery(oldjc, res.asQuery(), addcols='right').asCols() 73 | 74 | return res 75 | 76 | setattr(Columns, func.__name__, colfunc) 77 | setattr(Expr, '_' + func.__name__, func) 78 | return colfunc 79 | 80 | 81 | def injective(fname=None): 82 | def decorator(func): 83 | nonlocal fname 84 | if fname is None: 85 | fname = func.__name__ 86 | @property 87 | def colfunc(self, *args, **kwargs): 88 | if hasattr(self, fname + '_saved'): 89 | return getattr(self, fname + '_saved') 90 | 91 | res = func(self, *args, **kwargs) 92 | 93 | # another replica of augment 94 | res.groupbys = self.groupbys + res.groupbys 95 | 96 | if type(res) is Query and type(res.columns) is not Columns: 97 | for table in res.groupbys.getTables(): 98 | table.derivatives += res.columns.getTables()[0] 99 | 100 | res = addQuery(self.asQuery(), res.asQuery(), addcols='right').one 101 | # res.joincond &= self.joincond 102 | 103 | # STOP using primary as a way to track where the column came from, that's the role of the group bys 104 | object.__setattr__(self, fname + '_saved', res) 105 | return res 106 | setattr(Columns, func.__name__, colfunc) 107 | return colfunc 108 | return decorator 109 | 110 | 111 | def sqlfunc(strfunc): 112 | @lift 113 | @wraps(strfunc) 114 | def exprfunc(*exprs, **kwargs): 115 | return FuncExpr(strfunc, *exprs, **kwargs) 116 | return exprfunc 117 | 118 | 119 | def aggfunc(strfunc): 120 | @lift 121 | @wraps(strfunc) 122 | def exprfunc(*exprs, **kwargs): 123 | return AggExpr(strfunc, *exprs, **kwargs) 124 | @wraps(strfunc) 125 | def qfunc(q0, colname=None, **kwargs): 126 | q0 = copy(q0) 127 | if colname is not None: 128 | q0 = q0.fmap(lambda x: getattr(x, colname)) 129 | elif len(q0.columns) > 1: 130 | # this is so you can do milestones().count() instead of milestones().count('trid') 131 | q0.columns = q0.getPrimary() 132 | # q0 = q0.fmap(lambda x: q0.getPrimary()) 133 | return q0.aggregate(lambda x: exprfunc(x, **kwargs)) 134 | setattr(Query, strfunc.__name__[:-1], qfunc) 135 | return exprfunc 136 | 137 | 138 | def windowfunc(strfunc): 139 | @lift 140 | @wraps(strfunc) 141 | def exprfunc(*exprs, **kwargs): 142 | return WindowExpr(strfunc, *exprs, **kwargs) 143 | @wraps(strfunc) 144 | def qfunc(q0, *args, **kwargs): 145 | args = L(*args).fmap(lambda x: getattr(q0.columns, x) if type(x) is str else x) 146 | return q0.aggregate(lambda x: exprfunc(*args, **kwargs), ungroup=False) 147 | setattr(Query, strfunc.__name__[:-1], qfunc) 148 | @wraps(strfunc) 149 | def colfunc(col, *args, **kwargs): 150 | return qfunc(col.asQuery(), col, *args, **kwargs) 151 | setattr(Columns, strfunc.__name__, colfunc) 152 | return exprfunc 153 | 154 | 155 | # this dooesn't work... 156 | def memoize(fname=None): 157 | def decorator(func): 158 | nonlocal fname 159 | if fname is None: 160 | fname = func.__name__ 161 | @wraps(func) 162 | def mfunc(self, *args, **kwargs): 163 | if hasattr(self, func.__name__ + '_saved'): 164 | return getattr(self, func.__name__ + '_saved') 165 | res = func(*args, **kwargs) 166 | object.__setattr__(self, func.__name__ + '_saved', res) 167 | return res 168 | return mfunc 169 | return decorator 170 | 171 | 172 | # def addJC(func): 173 | # @wraps(func) 174 | # def mfunc(*args, **kwargs): 175 | # return func(*args, **kwargs).joinM() + Query.unit(*L(*args).filter(lambda x: isinstance(x, Columns))) 176 | # return mfunc 177 | 178 | 179 | class Lifted(object): 180 | def __init__(self, func): 181 | self.func = augment(func) 182 | setattr(Columns, func.__name__, self.func) 183 | def __call__(self, *args, **kwargs): 184 | return self.func(*args, **kwargs) 185 | 186 | 187 | class Kleisli(object): 188 | def __init__(self, func): 189 | self.func = augment(func) 190 | setattr(Columns, func.__name__, self.func) 191 | setattr(Query, func.__name__, lambda x: x.bind(self.func)) 192 | def __call__(self, *args, **kwargs): 193 | return self.func(*args, **kwargs) 194 | 195 | 196 | # %% ^━━━━━━━━━━━━━━━━ OPERATORS ━━━━━━━━━━━━━━━━━━━━^ 197 | 198 | @lift 199 | def __eq__(self, other): 200 | return EqExpr(self, other) 201 | @lift 202 | def __gt__(self, other): 203 | return BinOpExpr(">", self, other) 204 | @lift 205 | def __ge__(self, other): 206 | return BinOpExpr(">=", self, other) 207 | @lift 208 | def __lt__(self, other): 209 | return BinOpExpr("<", self, other) 210 | @lift 211 | def __le__(self, other): 212 | return BinOpExpr("<=", self, other) 213 | @lift 214 | def __ne__(self, other): 215 | return BinOpExpr("!=", self, other) 216 | @lift 217 | def __add__(self, other): 218 | if self.isTime() and type(other) is ConstExpr and isinstance(other.value, int): 219 | other = ConstExpr(timedelta(days = other.value)) 220 | return BinOpExpr("+", self, other) 221 | @lift 222 | def __sub__(self, other): 223 | if self.isTime() and other.isTime(): 224 | self = Expr._epoch_(self) 225 | other = Expr._epoch_(other) 226 | return BinOpExpr("-", self, other) 227 | @lift 228 | def __mul__(self, other): 229 | return BinOpExpr("*", self, other) 230 | @lift 231 | def __truediv__(self, other): 232 | return BinOpExpr(":: FLOAT /", self, other) 233 | @lift 234 | def __radd__(self, other): 235 | return BinOpExpr("+", other, self) 236 | @lift 237 | def __rsub__(self, other): 238 | return BinOpExpr("-", other, self) 239 | @lift 240 | def __rmul__(self, other): 241 | return BinOpExpr("*", other, self) 242 | @lift 243 | def __rtruediv__(self, other): 244 | return BinOpExpr(":: FLOAT /", other, self) 245 | @lift 246 | def __floordiv__(self, other): 247 | return BinOpExpr("/", self, other) 248 | @lift 249 | def __or__(self, other): 250 | return BinOpExpr("OR", self, other) 251 | @lift 252 | def __and__(self, other): 253 | return self & other 254 | @sqlfunc 255 | def __invert__(expr): 256 | if isinstance(expr, FuncExpr) and expr.func.__name__ == "__invert__": 257 | return str(expr).replace("NOT ", "") 258 | if isinstance(expr, FuncExpr) and expr.func.__name__ == "notnull_": 259 | return str(expr).replace("NOT NULL", "NULL") 260 | return f"NOT ({expr})" 261 | @sqlfunc 262 | def __neg__(expr): 263 | return f"-{expr}" 264 | 265 | 266 | # %% ^━━━━━━━━━━━━━━━━━━━ COLUMN FUNCTIONS ━━━━━━━━━━━━━━━━━^ 267 | 268 | @sqlfunc 269 | def round_(expr, interval=86400/4): 270 | return f"TIMESTAMP WITH TIME ZONE 'EPOCH' + FLOOR(EXTRACT(EPOCH FROM {expr}) / {interval}) * {interval} * INTERVAL '1 second'" 271 | @sqlfunc 272 | def zerodiv_(numer, denom): 273 | return f"COALESCE({numer} :: FLOAT / NULLIF({denom}, 0), 0)" 274 | @sqlfunc 275 | def len_(expr): 276 | return f"CHAR_LENGTH({expr})" 277 | @sqlfunc 278 | def in_(expr, *inlist): 279 | return f"{expr} IN {inlist}" 280 | @sqlfunc 281 | def like_(expr1, likestr=None): 282 | return f"{expr1} LIKE {likestr}" 283 | @sqlfunc 284 | def ilike_(expr1, likestr=None): 285 | return f"{expr1} ILIKE \'%{likestr}%\'" 286 | @sqlfunc 287 | def epoch_(expr): 288 | return f"EXTRACT(EPOCH FROM {expr})" 289 | @sqlfunc 290 | def randomize_(expr): 291 | return f"STRTOL(SUBSTRING(MD5({expr}), 1, 8), 16) :: FLOAT / (STRTOL('ffffffff', 16))" 292 | @sqlfunc 293 | def if_(cond, expr1, expr2): 294 | return f"CASE WHEN {cond} THEN {expr1} ELSE {expr2} END" 295 | @sqlfunc 296 | def ifen_(expr, cond): 297 | if type(cond) is FuncExpr and cond.func.__name__ == "ifen_": 298 | cond = cond.children[1] & cond.children[0] 299 | if type(expr) is FuncExpr and expr.func.__name__ == "ifen_": 300 | cond &= expr.children[1] 301 | expr = expr.children[0] 302 | return f"CASE WHEN {cond} THEN {expr} END" 303 | @sqlfunc 304 | def case_(*pairs): 305 | finalexpr = pairs[-1] 306 | res = "CASE " 307 | for i in range(0, len(pairs) - 2, 2): 308 | res += f"\n WHEN {pairs[i]} THEN {pairs[i+1]} " 309 | else: 310 | res += f"\n ELSE {finalexpr} \nEND" 311 | return res 312 | @sqlfunc 313 | def roundnum_(expr, interval=1, up=False): 314 | if not up: 315 | return f"FLOOR({expr} :: FLOAT / {interval}) * {interval}" 316 | else: 317 | return f"CEILING({expr} :: FLOAT / {interval}) * {interval}" 318 | @sqlfunc 319 | def cast_(expr, sqltype): 320 | strtype = str(sqltype)[1:-1] 321 | return f"({expr}) :: {strtype}" 322 | @sqlfunc 323 | def roundtime_(expr, interval=86400): 324 | # Expr._round_(Expr._epoch_(expr), interval) 325 | return f"TIMESTAMP WITH TIME ZONE 'EPOCH' + FLOOR(EXTRACT(EPOCH FROM {expr}) / {interval}) * {interval} * INTERVAL '1 second'" 326 | @sqlfunc 327 | def coalesce_(*exprs): 328 | return "COALESCE(" + ', '.join(map(str, exprs)) + ")" 329 | @sqlfunc 330 | def from_unixtime_(expr): 331 | return f"TIMESTAMP WITH TIME ZONE 'EPOCH' + {expr} * INTERVAL '1 SECOND'" 332 | @sqlfunc 333 | def log_(expr): 334 | return f"LN(GREATEST({expr}, 0) + 1)" 335 | @sqlfunc 336 | def exp_(expr): 337 | return f"EXP({expr})" 338 | @sqlfunc 339 | def floor_(expr): 340 | return f"FLOOR({expr})" 341 | @sqlfunc 342 | def isnull_(expr): 343 | if type(expr) is FuncExpr and expr.func.__name__ == "ifen_": 344 | return f'{expr.children[0]} IS NULL OR NOT {expr.children[1]}' 345 | return f"{expr} IS NULL" 346 | @sqlfunc 347 | def notnull_(expr): 348 | if type(expr) is FuncExpr and expr.func.__name__ == "ifen_": 349 | return f'{expr.children[0]} IS NOT NULL AND {expr.children[1]}' 350 | return f"{expr} IS NOT NULL" 351 | @sqlfunc 352 | def least_(*exprs): 353 | parts = ', '.join(map(str, exprs)) 354 | return f"LEAST({parts})" 355 | @sqlfunc 356 | def zscore_(expr, partitionexpr): 357 | return f"({expr} - AVG({expr}) OVER (PARTITION BY {partitionexpr})) :: FLOAT / STDDEV({expr}) OVER (PARTITION BY {partitionexpr})" 358 | @sqlfunc 359 | def greatest_(*exprs): 360 | parts = ', '.join(map(str, exprs)) 361 | return f"GREATEST({parts})" 362 | @sqlfunc 363 | def row_(partitionexpr, orderexpr): 364 | return f"ROW_NUMBER() OVER (PARTITION BY {partitionexpr} ORDER BY {orderexpr})" 365 | 366 | 367 | 368 | 369 | 370 | # %% ^━━━━━━━━━━━━━━━━━━ AGGREGATES ━━━━━━━━━━━━━━━━━━━━^ 371 | 372 | @aggfunc 373 | def count_(expr): 374 | return f'COUNT(DISTINCT {expr})' 375 | @aggfunc 376 | def avg_(expr, ntile=None): 377 | # expr = Expr._coalesce_(expr, 0) 378 | # return f'AVG({expr})' 379 | return f'AVG(COALESCE({expr}, 0))' 380 | @aggfunc 381 | def sum_(expr): 382 | return f'SUM({expr})' 383 | @aggfunc 384 | def max_(expr): 385 | return f'MAX({expr})' 386 | @aggfunc 387 | def min_(expr): 388 | return f'MIN({expr})' 389 | @aggfunc 390 | def any_(expr): 391 | return f'BOOL_OR({expr})' 392 | @aggfunc 393 | def all_(expr): 394 | return f'BOOL_AND({expr})' 395 | @aggfunc 396 | def median_(expr, partitions=None): 397 | return f'MEDIAN({expr})' 398 | 399 | 400 | 401 | # %% ^━━━━━━━━━━━━━━━━━━━━ WINDOW FUNCTIONS ━━━━━━━━━━━━━━━━━━^ 402 | 403 | def partstr(partitions): 404 | if partitions is not None: 405 | return 'OVER' + '(PARTITION BY' + ','.join(map(str, partitions)) + ')' 406 | return '' 407 | 408 | # @windowfunc 409 | # def ntile_(perc=100, expr=None, partitions=None, order=None): 410 | # return f'NTILE({perc}) OVER (PARTITION BY {partitions} ORDER BY {expr})' 411 | @windowfunc 412 | def listagg_(expr=None, order=None): 413 | return f'LISTAGG({expr}) WITHIN GROUP (ORDER BY {expr})' 414 | # @windowfunc 415 | # def quantileof_(perc=100, expr=None, partitions=None): 416 | # return f'PERCENTILE_DISC({perc/100}) WITHIN GROUP (ORDER BY {expr}){partitions}' 417 | # @windowfunc 418 | # def median_(expr, partitions=None): 419 | # return f'MEDIAN({expr}) OVER (PARTITION BY {partitions})' 420 | @windowfunc 421 | def rank_(order=None, partitions=None): 422 | return f'ROW_NUMBER() OVER (PARTITION BY {partitions} ORDER BY {order})' 423 | @windowfunc 424 | def first_(expr, *partitions, order): 425 | parts = ','.join(map(str, partitions)) 426 | return f'''FIRST_VALUE({expr}) OVER (PARTITION BY {parts} ORDER BY {orderby} ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING)''' 427 | @windowfunc 428 | def last_(expr, *partitions, order): 429 | parts = ','.join(map(str, partitions)) 430 | return f'''LAST_VALUE({expr}) OVER (PARTITION BY {parts} ORDER BY {orderby} ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING)''' 431 | # @windowfunc 432 | # def zscore_(expr, partitionexpr): 433 | # return f"({expr} - AVG({expr}) OVER (PARTITION BY {partitionexpr})) :: FLOAT / STDDEV({expr}) OVER (PARTITION BY {partitionexpr})" 434 | @windowfunc 435 | def lag_(expr, order=None, partitions=None): 436 | if partitions and not isinstance(partitions, list): 437 | partitions = [partitions] 438 | parts = '' 439 | if partitions: 440 | parts = ','.join(map(str, partitions)) 441 | parts = f'PARTITION BY {parts} ' 442 | return f'''LAG({expr}) OVER ({parts}ORDER BY {order})''' 443 | 444 | 445 | 446 | 447 | # %% ^━━━━━━━━━━━━━━━━━━━━━ MISC FUNCTIONS ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━^ 448 | 449 | @Lifted 450 | def ratio_(expr, cond): 451 | return expr.ifen_(cond).count_().zerodiv_(expr.count_()) 452 | 453 | @Lifted 454 | def between(self, bound, tlimit=None, ts='ts'): 455 | 456 | # todo: add a decorator for this 457 | try: 458 | tsvar = getattr(self, ts) 459 | except AttributeError: 460 | tsvars = self.items().filter(lambda x: '_ts' in x[0]).fmap(lambda x: x[1]) 461 | if tsvars: 462 | tsvar = tsvars[0] 463 | else: 464 | tsvar = self 465 | # raise AttributeError(f"No timestamp in {self}") 466 | 467 | 468 | if isinstance(tlimit, Columns) or isinstance(tlimit, str) or isinstance(tlimit, dt.datetime): 469 | return (tsvar > bound) & (tsvar < tlimit) 470 | elif isinstance(tlimit, int): 471 | if tlimit > 0: 472 | tlimit = timedelta(days=tlimit) 473 | return (tsvar > bound) & (tsvar < bound + tlimit) 474 | else: 475 | tlimit = timedelta(days=-tlimit) 476 | return (tsvar < bound) & (tsvar > bound - tlimit) 477 | else: 478 | raise TypeError("Between func: invalid upper limit") 479 | 480 | 481 | # %% ^━━━━━━━━━━━━━━━━━ OTHER FUNCTIONS ━━━━━━━━━━━━━━━━━━^ 482 | 483 | -------------------------------------------------------------------------------- /LambdaQuery/misc.py: -------------------------------------------------------------------------------- 1 | from copy import copy, deepcopy 2 | from datetime import timedelta 3 | from lenses import lens 4 | from functools import reduce 5 | import inspect 6 | import datetime as dt 7 | # import pandas as pd 8 | # import numpy as np 9 | 10 | def base10toN(num,n=36): 11 | """Change a to a base-n number. 12 | Up to base-36 is supported without special notation.""" 13 | num_rep = {10:'a', 11:'b', 12:'c', 13:'d', 14:'e', 15:'f', 16:'g', 17:'h', 18:'i', 19:'j', 20:'k', 21:'l', 22:'m', 14 | 23:'n', 24:'o', 25:'p', 26:'q', 27:'r', 28:'s', 29:'t', 30:'u', 31:'v', 32:'w', 33:'x', 34:'y', 35:'z'} 15 | new_num_string = '' 16 | current = num 17 | while current != 0: 18 | remainder = current % n 19 | if 36 > remainder > 9: 20 | remainder_string = num_rep[remainder] 21 | elif remainder >= 36: 22 | remainder_string = '(' + str(remainder) + ')' 23 | else: 24 | remainder_string = str(remainder) 25 | new_num_string = remainder_string + new_num_string 26 | current = current // n 27 | return new_num_string 28 | 29 | def memloc(self): 30 | return base10toN(id(self), 36)[-4:] 31 | 32 | def rename(newname): 33 | def decorator(f): 34 | f.__name__ = newname 35 | return f 36 | return decorator 37 | 38 | def patch(pcls): 39 | def decorator(func): 40 | setattr(pcls, func.__name__, func) 41 | return func 42 | return decorator 43 | 44 | def listunion(*lists): 45 | if lists == (): return [] 46 | lnew = [] 47 | for ll in lists: 48 | if not isinstance(ll, list): ll = [ll] 49 | for l in ll: 50 | if l not in lnew: 51 | lnew.append(l) 52 | return lnew 53 | 54 | def listminus(l1,l2): 55 | if not isinstance(l2, list): l2 = [l2] 56 | return [l for l in l1 if not l in l2] 57 | 58 | def listintersection(l1,l2): 59 | if not isinstance(l2, list): l2 = [l2] 60 | return [l for l in l1 if l in l2] 61 | 62 | 63 | # %% ^━━━━━━━━━━━━━━━━━ SUBJECT TO CHANGE ━━━━━━━━━━━━━━━━━━━━━━^ 64 | 65 | class KV(object): 66 | def __init__(self, key, value): 67 | self.key = key 68 | self.value = value 69 | # def key(self): 70 | # return self[0] 71 | # def value(self): 72 | # return self[1] 73 | def __iter__(self): 74 | return iter((self.key, self.value)) 75 | 76 | 77 | class L(list): 78 | 79 | def __init__(self, *args): 80 | args = [arg for arg in args if arg is not None] 81 | list.__init__(self, args) 82 | 83 | def __add__(self, other): 84 | return L(*listunion(self, other)) 85 | 86 | def __xor__(self, other): 87 | return L(*listintersection(self, other)) 88 | 89 | def __sub__(self, other): 90 | return L(*listminus(self, other)) 91 | 92 | def __and__(self, other): 93 | return L(*listintersection(self, other)) 94 | 95 | def __le__(self, other): 96 | return not bool(self - other) 97 | 98 | def __radd__(self, other): 99 | res = copy(self) 100 | if hasattr(other, '__iter__'): 101 | for el in other: 102 | res += el 103 | return res 104 | 105 | def __iadd__(self, other): 106 | # can't remember what this is for 107 | return NotImplemented 108 | 109 | def __repr__(self): 110 | return f'L<{list.__repr__(self)[1:-1]}>' 111 | 112 | def __rshift__(self, func): 113 | return func(self) 114 | 115 | def filter(self, cond): 116 | return L(*[el for el in self if cond(el)]) 117 | 118 | def zip(self, other): 119 | return L(*zip(self, other)) 120 | 121 | def fmap(self, ffunc): 122 | return L(*[ffunc(el) for el in self]) 123 | 124 | def modify(self, mfunc): 125 | for i, el in self.enumerate(): 126 | self[i] = mfunc(el) 127 | 128 | def combine(self): 129 | # import query 130 | return self.fold(lambda x, y: x @ y) 131 | 132 | def enumerate(self): 133 | return L(*[(k, v) for k, v in enumerate(self)]) 134 | 135 | def flatten(self): 136 | if not self: 137 | return L() 138 | return sum(self) 139 | 140 | def intersperse(self, separator): 141 | return separator.join(self.fmap(str)) 142 | 143 | def intersperse2(self, separator): 144 | return separator.join(self.fmap(lambda x: x.andrepr())) 145 | 146 | def bind(self, bfunc): 147 | return self.fmap(bfunc).flatten() 148 | 149 | def len(self): 150 | return len(self) 151 | 152 | def fold(self, ffunc=None, mzero=None, meth=None): 153 | # if not self: 154 | # print("Empty Fold") 155 | res = mzero if mzero is not None else self[0] 156 | ffunc = ffunc if ffunc is not None else getattr(mzero.__class__, meth) 157 | for el in self: 158 | res = ffunc(res, el) 159 | return res 160 | 161 | def sum(self): 162 | return self.flatten() 163 | 164 | def all(self): 165 | return all(self) 166 | # return self.fold(self[0].__class__.__and__) 167 | 168 | def pop(self): 169 | if self: 170 | list.pop(self) 171 | 172 | def any(self, cond=None): 173 | if cond: 174 | self = self.filter(cond) 175 | return any(self) 176 | # return self.fold(self[0].__class__.__or__) 177 | 178 | def groupby(self, gpbyfunc): 179 | res = {} 180 | for el in self: 181 | dummylabel = str(gpbyfunc(el)) 182 | if dummylabel in res: 183 | res[dummylabel] += el 184 | else: 185 | res[dummylabel] = L(el) 186 | return L(*[KV(key, value) for key, value in res.items()]) 187 | 188 | def partition(self, eqrel): 189 | # by an equivalence relation 190 | res = L() 191 | 192 | for el in self: 193 | for i, part in enumerate(res): 194 | if part.filter(lambda y: eqrel(el, y)): 195 | part += el 196 | res[i] = part 197 | break 198 | else: 199 | res += L(L(el)) 200 | return res 201 | 202 | def exists(self): 203 | return bool(self) 204 | 205 | def notExists(self): 206 | return not self.exists() 207 | 208 | def head(self): 209 | return L(self[0]) 210 | 211 | def copy(self): 212 | return copy(self) 213 | 214 | def sort(self, key=None): 215 | return L(*sorted(self, key=key)) 216 | 217 | def getTables(self): 218 | return self.bind(lambda x: x.getTables()) 219 | 220 | 221 | def updateUnique(self, other, makecopy=False): 222 | res = copy(self) if makecopy else self 223 | for key, value in other.items(): 224 | if key not in res:# and value not in res.values(): 225 | res[key] = value 226 | elif value not in res.values(): 227 | n = 0 228 | while True: 229 | if key + str(n) not in res: 230 | res[key + str(n)] = value 231 | break 232 | n += 1 233 | if makecopy: 234 | return res 235 | -------------------------------------------------------------------------------- /LambdaQuery/sql.py: -------------------------------------------------------------------------------- 1 | from LambdaQuery.reroute import * 2 | 3 | 4 | def sub_sql(self): 5 | return sql(self, reduce=False, subquery=True) 6 | 7 | 8 | def tableGen(self, reduce=True, debug=False, correlated=False, subquery=False): 9 | 10 | alltables = self.getTables() 11 | 12 | if correlated: 13 | correlated_tables = self.groupbys.getTables() 14 | alltables -= correlated_tables 15 | else: 16 | correlated_tables = L() 17 | 18 | fulltables = alltables.filter(lambda x: not x.leftjoin)# or x in self.groupbys.getTables()) 19 | ljtables = alltables.filter(lambda x: x.leftjoin)# and x not in self.groupbys.getTables()) 20 | 21 | res, remainingcond, addedtables, havings = '', copy(self.joincond), L(), L() 22 | wheres = self.joincond.children.filter(lambda x: not x.getTables()) 23 | cjed = {} 24 | 25 | while len(addedtables) < len(alltables): 26 | 27 | for table in fulltables + ljtables - addedtables: 28 | 29 | if cjed and table.leftjoin and not L(*cjed.keys()).filter(lambda x: x.leftjoin).any(): break 30 | 31 | # get the properties that only depend on one table to put in the where clause 32 | if table.leftjoin: 33 | wheres += remainingcond.children.filter(lambda x: table in x.getTables() 34 | and x.iswhere 35 | and not x.isJoin2() 36 | and not x.isagg()) 37 | else: 38 | wheres += remainingcond._filter(table, correlated_tables)\ 39 | .children.filter(lambda x: not x.isagg()) 40 | havings += remainingcond.children.filter(lambda x: x.isagg()) 41 | joins = remainingcond._filter(table, addedtables).children - wheres - havings 42 | 43 | if not addedtables: 44 | # first table 45 | res += f'\nFROM {sub_sql(table)}' 46 | 47 | elif joins.filter(lambda x: x.isJoin2()): 48 | if table in cjed: del cjed[table] 49 | # this one "works" and is proper 50 | jointype = 'LEFT ' if table.leftjoin and fulltables else '' 51 | joinstr = str(AndExpr(joins)).replace('AND', '\n AND') 52 | res += f"\n {jointype}JOIN {sub_sql(table)} ON {joinstr}" 53 | 54 | else: 55 | # cross joining 56 | if (table in cjed and cjed[table]) or len(alltables - addedtables) == 1: 57 | if table in cjed: del cjed[table] 58 | jointype = 'LEFT ' if table.leftjoin and fulltables else '' 59 | if joins: 60 | joinstr = str(AndExpr(joins)).replace('AND', '\n AND') 61 | res += f"\n {jointype}JOIN {sub_sql(table)} ON {joinstr}" 62 | else: 63 | res += f"\n {jointype}CROSS JOIN {sub_sql(table)}" 64 | else: 65 | cjed[table] = True 66 | continue 67 | 68 | remainingcond.children -= joins + wheres + havings 69 | addedtables.append(table) 70 | 71 | return res, wheres, havings 72 | 73 | 74 | def lastSeparator(self): 75 | return '\n' if len(self.split('\n ')[-1]) > 200 else '' 76 | 77 | 78 | def getGroupbys(self, havings=None, windows=None, reduce=False, subquery=False, debug=False): 79 | exprs = self.columns.values() 80 | if (reduce or subquery) and self.isagg() and not exprs.fmap(lambda x: x.isagg()).all(): 81 | groupbys = L(*range(1, exprs.filter(lambda x: not x.isagg() and not x.iswindow()).len() + 1)) 82 | groupbys += havings.bind(Expr.havingGroups).filter(lambda x: x not in exprs) 83 | # if subquery and not debug: 84 | # don't include the groupbys if the outermost query is an aggregate, 85 | # because we're cheating with functions like count_ 86 | groupbys += self.groupbys.filter(lambda x: x not in exprs) 87 | elif self.isagg() or debug: 88 | groupbys = self.groupbys 89 | else: 90 | return L() 91 | if groupbys and self.isagg(): 92 | groupbys += self.columns.values().filter(lambda x: isinstance(x, WindowExpr)).bind(lambda x: x.children).filter(lambda x: x not in exprs) 93 | if windows and self.isagg(): 94 | groupbys += windows 95 | return groupbys 96 | 97 | 98 | def sql(self, display=True, reduce=True, subquery=False, debug=False, correlated=False): 99 | 100 | if type(self) is Table: return str(self) 101 | if reduce: reduceQuery(self, debug=debug) 102 | 103 | if self in self.getTables(): 104 | raise EnvironmentError("INFINITE LOOP") 105 | 106 | # ==SELECT... 107 | selects = self.columns.items().sort(lambda x: x[1].isagg() or isinstance(x[1], WindowExpr)) 108 | showSql = lambda p: str(p[1]) + (f' AS {p[0]}' if p[0] != '' else f'') 109 | select_type = '' if self.isagg() else 'DISTINCT ' 110 | res = f'SELECT {select_type}\n ' + selects.fmap(showSql).intersperse(', \n ') 111 | 112 | # ==FROM... 113 | joinstr, wheres, havings = tableGen(self, reduce=reduce, debug=debug, correlated=correlated, subquery=subquery) 114 | res += joinstr 115 | 116 | # ==WHERE... 117 | if wheres: res += f'\nWHERE ' + wheres.intersperse2('\n AND ') 118 | 119 | # ==GROUP BY... 120 | windows = self.allDescendants().filter(lambda x: x.iswindow()).bind(lambda x: x.baseExprs()) 121 | groupbys = getGroupbys(self, havings, windows=windows, reduce=reduce, subquery=subquery, debug=debug) 122 | if groupbys: res += f'\nGROUP BY ' + groupbys.intersperse(', ') 123 | 124 | # ==HAVING... 125 | if havings: res += f'\nHAVING ' + havings.intersperse('\n AND ') 126 | 127 | # ==ORDER BY / LIMIT... 128 | if self.ordervar: res += f'\nORDER BY ' + self.ordervar.intersperse(', ') 129 | if self.limitvar: res += f'\nLIMIT {self.limitvar}' 130 | 131 | # ==ADJUSTMENTS 132 | if subquery: 133 | indent = "\n " if self.leftjoin else "\n " 134 | res = f'(--━━━━━━━━━━ SUBQUERY ━━━━━━━━━━--\n{res}\n--━━━━━━━━━━━━━━━━━━━━━━━━━━━━━--\n) AS {self.abbrev}'.replace("\n", indent) 135 | elif display: 136 | res = f'SQL() = \'\'\'\n{res}\n\'\'\'' 137 | 138 | return res 139 | 140 | 141 | # for pdb debugging 142 | import LambdaQuery.query 143 | @property 144 | def p_sql(self): 145 | print(sql(self, reduce=False)) 146 | LambdaQuery.query.Query.p = p_sql 147 | LambdaQuery.query.Query.sql = sql 148 | import LambdaQuery.expr 149 | LambdaQuery.expr.Columns.sql = lambda x: x.asQuery().sql() 150 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | The full documentation can be found here: http://lambdaquery.readthedocs.io. 2 | 3 | Write composable SQL in pure python. 4 | 5 | The question that we solve - why is writing SQL so hard? How is it that something describable in two sentences in English becomes a monster of a query? How does one not become lost navigating the scoping rules and the throwaway names that become rampant when you have more than one layer of aggregation? LambdaQuery is the answer. 6 | 7 | #### Features 8 | 9 | - VERY intuitive syntax - just like PonyORM except way better. The abstraction is right, and the syntax is very clean. 10 | - FULL COMPOSABLE. This cannot be emphasized enough. It should work just as if the tables were in-memory lists, so if it works for lists then it should work in LambdaQuery. 11 | - Figures out the right SQL, the right nesting of subqueries, the right ways to rereference out of scope columns, and the right columns to group by (which sometimes isn't so obvious when you have multiple layers of aggregation). 12 | 13 | 14 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | SPHINXPROJ = LambdaQuery 8 | SOURCEDIR = . 9 | BUILDDIR = _build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) -------------------------------------------------------------------------------- /docs/_build/doctrees/advanced.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xnmp/lambdaquery/4ede46cb8518ff393898ffaf12cebe530dc90627/docs/_build/doctrees/advanced.doctree -------------------------------------------------------------------------------- /docs/_build/doctrees/environment.pickle: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xnmp/lambdaquery/4ede46cb8518ff393898ffaf12cebe530dc90627/docs/_build/doctrees/environment.pickle -------------------------------------------------------------------------------- /docs/_build/doctrees/faq.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xnmp/lambdaquery/4ede46cb8518ff393898ffaf12cebe530dc90627/docs/_build/doctrees/faq.doctree -------------------------------------------------------------------------------- /docs/_build/doctrees/index.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xnmp/lambdaquery/4ede46cb8518ff393898ffaf12cebe530dc90627/docs/_build/doctrees/index.doctree -------------------------------------------------------------------------------- /docs/_build/doctrees/motivation.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xnmp/lambdaquery/4ede46cb8518ff393898ffaf12cebe530dc90627/docs/_build/doctrees/motivation.doctree -------------------------------------------------------------------------------- /docs/_build/doctrees/properties.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xnmp/lambdaquery/4ede46cb8518ff393898ffaf12cebe530dc90627/docs/_build/doctrees/properties.doctree -------------------------------------------------------------------------------- /docs/_build/doctrees/query.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xnmp/lambdaquery/4ede46cb8518ff393898ffaf12cebe530dc90627/docs/_build/doctrees/query.doctree -------------------------------------------------------------------------------- /docs/_build/doctrees/start.doctree: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xnmp/lambdaquery/4ede46cb8518ff393898ffaf12cebe530dc90627/docs/_build/doctrees/start.doctree -------------------------------------------------------------------------------- /docs/_build/html/.buildinfo: -------------------------------------------------------------------------------- 1 | # Sphinx build info version 1 2 | # This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. 3 | config: d9ae8b795f4daa4920bfe1de42544c95 4 | tags: 645f666f9bcd5a90fca523b33c5a78b7 5 | -------------------------------------------------------------------------------- /docs/_build/html/_images/schema.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xnmp/lambdaquery/4ede46cb8518ff393898ffaf12cebe530dc90627/docs/_build/html/_images/schema.png -------------------------------------------------------------------------------- /docs/_build/html/_sources/advanced.rst.txt: -------------------------------------------------------------------------------- 1 | 2 | Advanced 3 | ========= 4 | 5 | 6 | Grouping By 7 | ----------- 8 | 9 | Grouping by 10 | 11 | 12 | Last Before 13 | ----------- 14 | 15 | 16 | 17 | 18 | Using Kleisli and Lifted 19 | ------------------------ 20 | 21 | If you wrap these you get a few benefits: 22 | 23 | * The resulting columns have more readable names 24 | * You can use the resulting functions as methods 25 | * They add on the group bys of their source. 26 | 27 | 28 | How it Works 29 | ============ 30 | 31 | Basically the internal representation of a query is as a graph. Query combinators are just transformations on this graph. Before generating the SQL, LambdaQuery performs a reduction algorithm on this graph, and then the reduced graph is compiled to SQL. To see the SQL of the unreduced graph, use the ``reduce=False`` option for the ``Query.sql`` method. -------------------------------------------------------------------------------- /docs/_build/html/_sources/faq.rst.txt: -------------------------------------------------------------------------------- 1 | FAQ 2 | ==== 3 | 4 | Coming soon! 5 | 6 | Please raise an issue on Github `here `_. -------------------------------------------------------------------------------- /docs/_build/html/_sources/index.rst.txt: -------------------------------------------------------------------------------- 1 | .. LambdaQuery documentation master file, created by 2 | sphinx-quickstart on Sat Oct 7 18:35:07 2017. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | 7 | Welcome to λQuery! 8 | ======================================= 9 | 10 | .. toctree:: 11 | :maxdepth: 2 12 | :caption: Contents 13 | 14 | motivation 15 | start 16 | query 17 | properties 18 | advanced 19 | faq 20 | 21 | This is a library that enables one to write composable SQL in pure python. It is heavily functional, enabling one to chain query combinators in incidentally very similar to the `Rabbit query language `_. 22 | 23 | The question that we solve - why is writing SQL so hard? How is it that something describable in two sentences in English becomes a monster of a query? How does one not become lost navigating the scoping rules and the throwaway names that become rampant when you have more than one layer of aggregation? LambdaQuery is the answer. 24 | 25 | The main goals of LambdaQuery are to be a query API that: 26 | 27 | * Removes as much syntactic noise as possible. 28 | 29 | * Joining by foreign keys automatically. 30 | * Handles all of the red tape in SQL. 31 | * Automatically assigns the throaway names to subqueries and columns of subqueries. 32 | * Knows which tables should be left joined and handles the null cases. 33 | * Knows which relationships are one to one, and choosing the group bys appropriately to preserve structure of the rows. 34 | 35 | * Syntax that is very similar to manipulating in-memory Python lists. 36 | 37 | * Being able to work with particular elements of a list or the list as a whole. 38 | * Having CLEAN syntax just like actual Python. 39 | 40 | * Maximize code reusability. 41 | 42 | * Define functions from rows to rows (a one-to-one relationship), from rows to lists (a one-to-many relationship), from lists to lists, or lists to rows (aggregation). 43 | * Such functions can include data from other tables. 44 | 45 | * Minimize boilerplate - setting up tables should be as easy as it is in SQLAlchemy. 46 | 47 | 48 | Contributing 49 | ------------ 50 | 51 | If you have a question then please raise an issue on Github `here `_. 52 | 53 | 54 | -------------------------------------------------------------------------------- /docs/_build/html/_sources/motivation.rst.txt: -------------------------------------------------------------------------------- 1 | Motivation 2 | ========== 3 | 4 | SQL is a pain in the ass to write. 5 | 6 | Shortcomings of SQL 7 | -------------------- 8 | 9 | 10 | -------------------------------------------------------------------------------- /docs/_build/html/_sources/properties.rst.txt: -------------------------------------------------------------------------------- 1 | 2 | Defining Properties 3 | =================== 4 | 5 | This is where LambdaQuery really shines. 6 | 7 | 8 | Injective (One-to-One) Properties 9 | --------------------------------- 10 | 11 | jjj 12 | 13 | 14 | One-to-Many Properties 15 | ---------------------- 16 | 17 | Use Kleisli decorator 18 | 19 | 20 | Composition 21 | =========== 22 | 23 | We can freely compose these functions. -------------------------------------------------------------------------------- /docs/_build/html/_sources/query.rst.txt: -------------------------------------------------------------------------------- 1 | 2 | Querying 3 | ======== 4 | 5 | For this we're going to be using the same examples as can be found `here `_. That is we have a database for a university, with four tables: 6 | 7 | * Schools - a university may have several schools, for example the school of science, the school of arts, and the school of business. The school table has the following columns: 8 | 9 | * A code (primary key) 10 | * A name 11 | * A campus - north campus, south campus, etc. 12 | 13 | * Programs - each school may offer several programs, or degrees - a bachelor of commerse, a bachelor of arts, or a master of arts. The program title has the following columns: 14 | 15 | * A code (primary key) 16 | * A school code (foreign key) 17 | * A title (e.g. bachelor of commerce) 18 | * A degree type - bachelor's, masters, phd, etc. 19 | 20 | * Departments - for example, the physics department might be a part of the school of science. Each department has: 21 | 22 | * A code (primary key) 23 | * A name 24 | * A school code (foreign key) 25 | 26 | * Courses - Departments offer courses, this is pretty self explanatory. each course has 27 | 28 | * A course code (primary key) 29 | * A department code (foreign key) 30 | * A number of credits 31 | * A description 32 | * A title 33 | 34 | This is summarized in the following diagram: 35 | 36 | .. image:: images/schema.png 37 | 38 | 39 | The Query Object 40 | ---------------- 41 | 42 | Tables are set up in the "Examples" folder of the github repo located at https://github.com/xnmp/lambdaquery. The boilerplate code should be fairly self-explanatory and it's worth glancing over it to see the syntax. 43 | 44 | Use the ``Query.sql`` method to display the SQL of a query. 45 | 46 | .. code-block:: python 47 | 48 | ex1 = School.query() 49 | print(ex1.sql()) 50 | 51 | This will give the result: 52 | 53 | .. code-block:: mysql 54 | 55 | SELECT DISTINCT 56 | sc_kda8.school_code AS code, 57 | sc_kda8.name AS name, 58 | sc_kda8.campus AS campus 59 | FROM school AS sc_kda8 60 | 61 | 62 | 63 | 64 | Filtering 65 | --------- 66 | 67 | Use the ``.filter`` method to filter: 68 | 69 | .. code-block:: python 70 | 71 | School.query().filter(lambda x: x.campus == 'south') 72 | 73 | Since this construction is so common, this can be abbreviated down to: 74 | 75 | .. code-block:: python 76 | 77 | School.query(lambda x: x.campus == 'south') 78 | 79 | The resulting SQL looks like: 80 | 81 | .. code-block:: mysql 82 | 83 | SELECT DISTINCT 84 | sc_pob4.school_code AS code, 85 | sc_pob4.name AS name, 86 | sc_pob4.campus AS campus 87 | FROM school AS sc_pob4 88 | WHERE (sc_pob4.campus = 'south') 89 | 90 | 91 | 92 | Selecting 93 | --------- 94 | 95 | To demonstrate selecting, we use the ``fmap`` method of the query object. Again, we can think of a query like a list, and the ``fmap`` method is like mapping a function over that list. Selection looks like: 96 | 97 | .. code-block:: python 98 | 99 | School.query().fmap(lambda x: x.name % x.campus) 100 | 101 | Here we can think of the query as a list of dictionaries where the entries of the dictionary can be accessed by using an attribute name. The ``%`` operator herre can be thought of the operator that combinbes two dictionaries. 102 | 103 | Note that because this is so common, a simpler way to do this is via the ``getitem`` method as follows: 104 | 105 | .. code-block:: python 106 | 107 | School.query()['name','campus'] 108 | 109 | This will give the result: 110 | 111 | .. code-block:: mysql 112 | 113 | SELECT DISTINCT 114 | sc_pob4.name AS name, 115 | sc_pob4.campus AS campus 116 | FROM school AS sc_pob4 117 | 118 | Now there's always several ways to do things in LambdaQuery, and its strength lies in using a generator function to "unroll" a query. We can do this via the ``yield`` keyword, so that when we write ``sc0 = yield School.query()`` this can be thought of as ``for sc0 in School.query():``. Then ``sc0`` is a single school, and we can apply functions that work on single schools to ``sc0``, such as, say, the number of departments it has. 119 | 120 | For reference, the above is written using the generator function as: 121 | 122 | .. code-block:: python 123 | 124 | @do(Query) 125 | def ex4(): 126 | sc0 = yield School.query() 127 | returnM (sc0.name, sc0.campus) 128 | 129 | which may be read as "for each school, return its name and campus". 130 | 131 | 132 | Joining 133 | ------- 134 | 135 | Joining is very simple when we only have one foreign key. For example, if we have a school ``sc0`` and we want its departments, then this is simply ``sc0.departments()`` which is itself a query object. Note the open-and-close bracket at the end - this is to signify a one-to-many relationship. For a one-to-one relationship such as the school of a department ``dept0``, we don't need the brackets and this is just ``dept0.school``. 136 | 137 | For example, suppose we wanted: 138 | 139 | for each department, return its name, the name of its school, and the campus of its school. 140 | 141 | This is written as: 142 | 143 | .. code-block:: python 144 | 145 | @do(Query) 146 | def ex6(): 147 | dept0 = yield Department.query() 148 | returnM ( 149 | dept0.name, 150 | dept0.school.name, 151 | dept0.school.campus 152 | ) 153 | 154 | And the SQL generated is: 155 | 156 | .. code-block:: mysql 157 | 158 | SELECT DISTINCT 159 | dept_ilso.name AS name, 160 | sc_im20.name AS name0, 161 | sc_im20.campus AS campus 162 | FROM department AS dept_ilso 163 | JOIN school AS sc_im20 ON (sc_im20.school_code = dept_ilso.school_code) 164 | 165 | 166 | 167 | 168 | Aggregation 169 | =========== 170 | 171 | Now the query object has the ``min``, ``max``, ``avg``, ``sum``, and ``count`` methods, which collapse a query down into one number. So suppose for each school, we want the number of departments. This is written as: 172 | 173 | .. code-block:: python 174 | 175 | @do(Query) 176 | def ex5(): 177 | sc0 = yield School.query() 178 | returnM ( 179 | sc0.name, 180 | sc0.departments().count() 181 | ) 182 | 183 | The generated SQL is: 184 | 185 | .. code-block:: mysql 186 | 187 | SELECT 188 | sc_xzqo.name AS name, 189 | COUNT(DISTINCT dept_y04o.dept_code) AS count_code 190 | FROM school AS sc_xzqo 191 | JOIN department AS dept_y04o ON (dept_y04o.school_code = sc_xzqo.school_code) 192 | GROUP BY 1 193 | 194 | 195 | Encapsulation 196 | --------------- 197 | 198 | Here we get our first glimpse into the enormous amount of composability offered by LambdaQuery. We can write the above a function as follows:: 199 | 200 | def num_dept(self): 201 | return self.departments().count() 202 | 203 | And now the above can be written as:: 204 | 205 | @do(Query) 206 | def ex5(): 207 | sc0 = yield School.query() 208 | returnM ( 209 | sc0.name, 210 | num_dept(sc0) 211 | ) 212 | 213 | If we want to use it as if it were an attribute, then we need to use the ``@injective`` decorator:: 214 | 215 | @injective() 216 | def num_dept(self): 217 | return self.departments().count() 218 | 219 | This enables us to write:: 220 | 221 | @do(Query) 222 | def ex5(): 223 | sc0 = yield School.query() 224 | returnM ( 225 | sc0.name, 226 | sc0.num_dept 227 | ) 228 | 229 | In fact, as before we can now write ``School.query()['name','num_dept']`` to get the same result. 230 | 231 | We can even filter on this: ``School.query(lambda x: x.num_dept > 3)`` returns the SQL: 232 | 233 | .. code-block:: mysql 234 | 235 | SELECT 236 | sc_7488.school_code AS code, 237 | sc_7488.name AS name, 238 | sc_7488.campus AS campus 239 | FROM school AS sc_7488 240 | JOIN department AS dept_751s ON (dept_751s.school_code = sc_7488.school_code) 241 | GROUP BY 1, 2, 3 242 | HAVING (COUNT(DISTINCT dept_751s.dept_code) > 3) 243 | 244 | In every respect the ``num_dept`` property that we have just defined may be treated as if it were just another column. 245 | 246 | 247 | Explicit Grouping By 248 | ----------------------- 249 | 250 | Grouping by is a matter of using the functions ``max_``, ``min_``, ``count_`` (with an underscore). Note that these functions take a single row to a single row, and are the only cases of breaking the intuition of thinking of queries as lists. Such functions should only be used inside the ``returnM``. 251 | 252 | Suppose we wanted to get the average number of credits of courses with a code that begins with "100". This looks like:: 253 | 254 | @do(Query) 255 | def ex8(): 256 | cs0 = yield Course.query() 257 | returnM ( 258 | cs0.no.like_('100%'), 259 | cs0.credits.avg_() 260 | ) 261 | 262 | Generated SQL: 263 | 264 | .. code-block:: mysql 265 | 266 | SELECT 267 | course_ie1k.course_code LIKE '100%' AS like_no, 268 | AVG(COALESCE(course_ie1k.credits, 0)) AS avg_credits 269 | FROM course AS course_ie1k 270 | GROUP BY 1 271 | 272 | 273 | Iteration 274 | ========= 275 | 276 | Suppose for each department, we wanted to know the number of 100 series courses it offered, and the same for 200 series, 300 series, and 400 series. In SQL, this looks like: 277 | 278 | .. code-block:: mysql 279 | 280 | SELECT 281 | dept_ek40.name AS name, 282 | COUNT(DISTINCT CASE WHEN (course_fsu0.course_code > 100) AND (course_fsu0.course_code < 199) THEN course_fsu0.course_code END) AS count_no, 283 | COUNT(DISTINCT CASE WHEN (course_fsu0.course_code > 200) AND (course_fsu0.course_code < 299) THEN course_fsu0.course_code END) AS count_no0, 284 | COUNT(DISTINCT CASE WHEN (course_fsu0.course_code > 300) AND (course_fsu0.course_code < 399) THEN course_fsu0.course_code END) AS count_no1, 285 | COUNT(DISTINCT CASE WHEN (course_fsu0.course_code > 400) AND (course_fsu0.course_code < 499) THEN course_fsu0.course_code END) AS count_no2 286 | FROM department AS dept_ek40 287 | JOIN course AS course_fsu0 ON (course_fsu0.dept_code = dept_ek40.dept_code) 288 | GROUP BY 1 289 | 290 | To write this out, one would usually copy and paste the column and replace the number every time. We can do better. To illustrate, let's first define a property for if the code is of a particular series number:: 291 | 292 | @Lifted 293 | def coursestart(course, start): 294 | return (course.no > start) & (course.no < start + 99) 295 | 296 | Note that this function can take a second argument: ``start``. Now let's use it like so:: 297 | 298 | @do(Query) 299 | def ex17(): 300 | dept0 = yield Department.query() 301 | returnM ( 302 | dept0.name, 303 | *[dept0.courses(lambda x: x.coursestart(i*100)).count() 304 | for i in range(1,5)] 305 | ) 306 | 307 | This is course what generated the original SQL. 308 | 309 | 310 | Left Joining 311 | ============= 312 | 313 | This is a matter of using the ``lj`` method. Intuitively, this turns an empty list into a list containing a single null value, and keeps every other list the same. 314 | 315 | For example, suppose we want 316 | 317 | for each department, its name, and the number of courses with greater than 2 credits. 318 | 319 | Note that it may be that a department offers no courses with greater than 2 credits, so this is a case where we need to left join. Namely:: 320 | 321 | @do(Query) 322 | def ex15(): 323 | dept0 = yield Department.query() 324 | returnM ( 325 | dept0.name, 326 | dept0.courses(lambda x: x.credits > 2).lj().count().coalesce_(0) 327 | ) 328 | 329 | Generated SQL: 330 | 331 | .. code-block:: mysql 332 | 333 | SELECT 334 | dept_18gw.name AS name, 335 | COALESCE(COUNT(DISTINCT course_l_rwa0.course_code), 0) AS coalesce_count_no 336 | FROM department AS dept_18gw 337 | LEFT JOIN course AS course_l_rwa0 ON (course_l_rwa0.credits > 2) 338 | AND (course_l_rwa0.dept_code = dept_18gw.dept_code) 339 | GROUP BY 1 340 | 341 | 342 | Left Joining Dependent Tables 343 | ------------------------------ 344 | 345 | One annoying thing about SQL can be seen in the following example: suppose we wanted, for each school, the number of departments that had at least one course that offered a course with at least 2 credits. 346 | 347 | We have to remember that the course table must be left joined as well, and any reference to the department table must come with the additional provisio that the course table is not null. 348 | 349 | LambdaQuery:: 350 | 351 | @do(Query) 352 | def ex15(): 353 | sc0 = yield School.query() 354 | returnM ( 355 | sc0.name, 356 | sc0.departments(lambda x: x.courses(lambda y: y.credits > 2) 357 | .exists()) 358 | .lj().count().coalesce_(0) 359 | ) 360 | 361 | The generated SQL shows that all of the preceding concerns are handled for us: 362 | 363 | .. code-block:: mysql 364 | 365 | SELECT 366 | sc_bgo0.name AS name, 367 | COALESCE(COUNT(DISTINCT CASE WHEN course_l_m7nk.course_code IS NOT NULL THEN dept_l_m5yw.dept_code END), 0) AS coalesce_count_code 368 | FROM school AS sc_bgo0 369 | LEFT JOIN department AS dept_l_m5yw ON (dept_l_m5yw.school_code = sc_bgo0.school_code) 370 | LEFT JOIN course AS course_l_m7nk ON course_l_m7nk.course_code IS NOT NULL 371 | AND (course_l_m7nk.dept_code = dept_l_m5yw.dept_code) 372 | AND (course_l_m7nk.credits > 2) 373 | GROUP BY 1 374 | 375 | Pretty cool eh? This really is just the beginning. Most ORMs start getting really convoluted when you write more complex queries, and it's usually harder than just writing raw SQL. With LambdaQuery it remains simple and intuitive, and the SQL compiler works out all the bits of SQL logic so you don't have to. 376 | 377 | We give just one more example here as a taste, and this is about as complex as it gets using the tables that we have. Suppose we want, for each school with a course that has at least 5 programs with a title that matches the school's name, the average number of high credit courses offered by its departments. Here a high credit course is one with greater than 3 departments. 378 | 379 | Here we go:: 380 | 381 | @do(Query) 382 | def ex19(): 383 | sc0 = yield School.query(lambda x: x.programs(lambda y: y.title == x.name) 384 | .count() >= 5) 385 | returnM ( 386 | sc0.name, 387 | sc0.departments() 388 | .fmap(lambda x: x.courses(lambda y: y.credits > 3).count()) 389 | .lj().avg() 390 | ) 391 | 392 | 393 | Generated SQL: 394 | 395 | .. code-block:: mysql 396 | 397 | SELECT 398 | query_upi0.reroute_uqjc AS name, 399 | AVG(COALESCE(query_upi0.count_no_upl4, 0)) AS avg_count_no 400 | FROM ( 401 | SELECT 402 | sc_j3io.school_code AS reroute_upew, 403 | sc_j3io.name AS reroute_uqjc, 404 | COUNT(DISTINCT course_l_tti0.course_code) AS count_no_upl4 405 | FROM school AS sc_j3io 406 | JOIN department AS dept_j0k8 ON (dept_j0k8.school_code = sc_j3io.school_code) 407 | JOIN program AS prog_iy3k ON (prog_iy3k.school_code = sc_j3io.school_code) 408 | AND (prog_iy3k.title = sc_j3io.name) 409 | LEFT JOIN course AS course_l_tti0 ON (course_l_tti0.dept_code = dept_j0k8.dept_code) 410 | AND (course_l_tti0.credits > 3) 411 | GROUP BY 1, 2, dept_j0k8.dept_code 412 | HAVING (COUNT(DISTINCT prog_iy3k.prog_code) >= 5) 413 | ) AS query_upi0 414 | JOIN program AS prog_copy_iypc ON (prog_copy_iypc.school_code = query_upi0.reroute_upew) 415 | AND (prog_copy_iypc.title = query_upi0.reroute_uqjc) 416 | GROUP BY 1 417 | HAVING (COUNT(DISTINCT prog_copy_iypc.prog_code) >= 5) 418 | 419 | One of the things to iron out in future versions is the redundant condition ``HAVING (COUNT(DISTINCT prog_copy_q6g8.prog_code) >= 5)`` appearing in both the innermost query and the outermost query. 420 | -------------------------------------------------------------------------------- /docs/_build/html/_sources/start.rst.txt: -------------------------------------------------------------------------------- 1 | 2 | Installation 3 | ======================================= 4 | 5 | Complete the following instructions: 6 | 7 | * ``git clone https://github.com/xnmp/lambdaquery.git`` 8 | * ``cd LambdaQuery`` 9 | * ``python setup.py install``. 10 | 11 | 12 | Setting up Tables 13 | ================== 14 | 15 | In ``Examples/example._tables.py`` there is an example of setting up tables. The structure of this database is outlined in these docs under the "Querying" section. -------------------------------------------------------------------------------- /docs/_build/html/_static/ajax-loader.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xnmp/lambdaquery/4ede46cb8518ff393898ffaf12cebe530dc90627/docs/_build/html/_static/ajax-loader.gif -------------------------------------------------------------------------------- /docs/_build/html/_static/alabaster.css: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | @import url("basic.css"); 54 | 55 | /* -- page layout ----------------------------------------------------------- */ 56 | 57 | body { 58 | font-family: 'goudy old style', 'minion pro', 'bell mt', Georgia, 'Hiragino Mincho Pro', serif; 59 | font-size: 17px; 60 | background-color: #fff; 61 | color: #000; 62 | margin: 0; 63 | padding: 0; 64 | } 65 | 66 | 67 | div.document { 68 | width: 940px; 69 | margin: 30px auto 0 auto; 70 | } 71 | 72 | div.documentwrapper { 73 | float: left; 74 | width: 100%; 75 | } 76 | 77 | div.bodywrapper { 78 | margin: 0 0 0 220px; 79 | } 80 | 81 | div.sphinxsidebar { 82 | width: 220px; 83 | font-size: 14px; 84 | line-height: 1.5; 85 | } 86 | 87 | hr { 88 | border: 1px solid #B1B4B6; 89 | } 90 | 91 | div.body { 92 | background-color: #fff; 93 | color: #3E4349; 94 | padding: 0 30px 0 30px; 95 | } 96 | 97 | div.body > .section { 98 | text-align: left; 99 | } 100 | 101 | div.footer { 102 | width: 940px; 103 | margin: 20px auto 30px auto; 104 | font-size: 14px; 105 | color: #888; 106 | text-align: right; 107 | } 108 | 109 | div.footer a { 110 | color: #888; 111 | } 112 | 113 | p.caption { 114 | font-family: inherit; 115 | font-size: inherit; 116 | } 117 | 118 | 119 | div.relations { 120 | display: none; 121 | } 122 | 123 | 124 | div.sphinxsidebar a { 125 | color: #444; 126 | text-decoration: none; 127 | border-bottom: 1px dotted #999; 128 | } 129 | 130 | div.sphinxsidebar a:hover { 131 | border-bottom: 1px solid #999; 132 | } 133 | 134 | div.sphinxsidebarwrapper { 135 | padding: 18px 10px; 136 | } 137 | 138 | div.sphinxsidebarwrapper p.logo { 139 | padding: 0; 140 | margin: -10px 0 0 0px; 141 | text-align: center; 142 | } 143 | 144 | div.sphinxsidebarwrapper h1.logo { 145 | margin-top: -10px; 146 | text-align: center; 147 | margin-bottom: 5px; 148 | text-align: left; 149 | } 150 | 151 | div.sphinxsidebarwrapper h1.logo-name { 152 | margin-top: 0px; 153 | } 154 | 155 | div.sphinxsidebarwrapper p.blurb { 156 | margin-top: 0; 157 | font-style: normal; 158 | } 159 | 160 | div.sphinxsidebar h3, 161 | div.sphinxsidebar h4 { 162 | font-family: 'Garamond', 'Georgia', serif; 163 | color: #444; 164 | font-size: 24px; 165 | font-weight: normal; 166 | margin: 0 0 5px 0; 167 | padding: 0; 168 | } 169 | 170 | div.sphinxsidebar h4 { 171 | font-size: 20px; 172 | } 173 | 174 | div.sphinxsidebar h3 a { 175 | color: #444; 176 | } 177 | 178 | div.sphinxsidebar p.logo a, 179 | div.sphinxsidebar h3 a, 180 | div.sphinxsidebar p.logo a:hover, 181 | div.sphinxsidebar h3 a:hover { 182 | border: none; 183 | } 184 | 185 | div.sphinxsidebar p { 186 | color: #555; 187 | margin: 10px 0; 188 | } 189 | 190 | div.sphinxsidebar ul { 191 | margin: 10px 0; 192 | padding: 0; 193 | color: #000; 194 | } 195 | 196 | div.sphinxsidebar ul li.toctree-l1 > a { 197 | font-size: 120%; 198 | } 199 | 200 | div.sphinxsidebar ul li.toctree-l2 > a { 201 | font-size: 110%; 202 | } 203 | 204 | div.sphinxsidebar input { 205 | border: 1px solid #CCC; 206 | font-family: 'goudy old style', 'minion pro', 'bell mt', Georgia, 'Hiragino Mincho Pro', serif; 207 | font-size: 1em; 208 | } 209 | 210 | div.sphinxsidebar hr { 211 | border: none; 212 | height: 1px; 213 | color: #AAA; 214 | background: #AAA; 215 | 216 | text-align: left; 217 | margin-left: 0; 218 | width: 50%; 219 | } 220 | 221 | /* -- body styles ----------------------------------------------------------- */ 222 | 223 | a { 224 | color: #004B6B; 225 | text-decoration: underline; 226 | } 227 | 228 | a:hover { 229 | color: #6D4100; 230 | text-decoration: underline; 231 | } 232 | 233 | div.body h1, 234 | div.body h2, 235 | div.body h3, 236 | div.body h4, 237 | div.body h5, 238 | div.body h6 { 239 | font-family: 'Garamond', 'Georgia', serif; 240 | font-weight: normal; 241 | margin: 30px 0px 10px 0px; 242 | padding: 0; 243 | } 244 | 245 | div.body h1 { margin-top: 0; padding-top: 0; font-size: 240%; } 246 | div.body h2 { font-size: 180%; } 247 | div.body h3 { font-size: 150%; } 248 | div.body h4 { font-size: 130%; } 249 | div.body h5 { font-size: 100%; } 250 | div.body h6 { font-size: 100%; } 251 | 252 | a.headerlink { 253 | color: #DDD; 254 | padding: 0 4px; 255 | text-decoration: none; 256 | } 257 | 258 | a.headerlink:hover { 259 | color: #444; 260 | background: #EAEAEA; 261 | } 262 | 263 | div.body p, div.body dd, div.body li { 264 | line-height: 1.4em; 265 | } 266 | 267 | div.admonition { 268 | margin: 20px 0px; 269 | padding: 10px 30px; 270 | background-color: #EEE; 271 | border: 1px solid #CCC; 272 | } 273 | 274 | div.admonition tt.xref, div.admonition code.xref, div.admonition a tt { 275 | background-color: #FBFBFB; 276 | border-bottom: 1px solid #fafafa; 277 | } 278 | 279 | div.admonition p.admonition-title { 280 | font-family: 'Garamond', 'Georgia', serif; 281 | font-weight: normal; 282 | font-size: 24px; 283 | margin: 0 0 10px 0; 284 | padding: 0; 285 | line-height: 1; 286 | } 287 | 288 | div.admonition p.last { 289 | margin-bottom: 0; 290 | } 291 | 292 | div.highlight { 293 | background-color: #fff; 294 | } 295 | 296 | dt:target, .highlight { 297 | background: #FAF3E8; 298 | } 299 | 300 | div.warning { 301 | background-color: #FCC; 302 | border: 1px solid #FAA; 303 | } 304 | 305 | div.danger { 306 | background-color: #FCC; 307 | border: 1px solid #FAA; 308 | -moz-box-shadow: 2px 2px 4px #D52C2C; 309 | -webkit-box-shadow: 2px 2px 4px #D52C2C; 310 | box-shadow: 2px 2px 4px #D52C2C; 311 | } 312 | 313 | div.error { 314 | background-color: #FCC; 315 | border: 1px solid #FAA; 316 | -moz-box-shadow: 2px 2px 4px #D52C2C; 317 | -webkit-box-shadow: 2px 2px 4px #D52C2C; 318 | box-shadow: 2px 2px 4px #D52C2C; 319 | } 320 | 321 | div.caution { 322 | background-color: #FCC; 323 | border: 1px solid #FAA; 324 | } 325 | 326 | div.attention { 327 | background-color: #FCC; 328 | border: 1px solid #FAA; 329 | } 330 | 331 | div.important { 332 | background-color: #EEE; 333 | border: 1px solid #CCC; 334 | } 335 | 336 | div.note { 337 | background-color: #EEE; 338 | border: 1px solid #CCC; 339 | } 340 | 341 | div.tip { 342 | background-color: #EEE; 343 | border: 1px solid #CCC; 344 | } 345 | 346 | div.hint { 347 | background-color: #EEE; 348 | border: 1px solid #CCC; 349 | } 350 | 351 | div.seealso { 352 | background-color: #EEE; 353 | border: 1px solid #CCC; 354 | } 355 | 356 | div.topic { 357 | background-color: #EEE; 358 | } 359 | 360 | p.admonition-title { 361 | display: inline; 362 | } 363 | 364 | p.admonition-title:after { 365 | content: ":"; 366 | } 367 | 368 | pre, tt, code { 369 | font-family: 'Consolas', 'Menlo', 'Deja Vu Sans Mono', 'Bitstream Vera Sans Mono', monospace; 370 | font-size: 0.9em; 371 | } 372 | 373 | .hll { 374 | background-color: #FFC; 375 | margin: 0 -12px; 376 | padding: 0 12px; 377 | display: block; 378 | } 379 | 380 | img.screenshot { 381 | } 382 | 383 | tt.descname, tt.descclassname, code.descname, code.descclassname { 384 | font-size: 0.95em; 385 | } 386 | 387 | tt.descname, code.descname { 388 | padding-right: 0.08em; 389 | } 390 | 391 | img.screenshot { 392 | -moz-box-shadow: 2px 2px 4px #EEE; 393 | -webkit-box-shadow: 2px 2px 4px #EEE; 394 | box-shadow: 2px 2px 4px #EEE; 395 | } 396 | 397 | table.docutils { 398 | border: 1px solid #888; 399 | -moz-box-shadow: 2px 2px 4px #EEE; 400 | -webkit-box-shadow: 2px 2px 4px #EEE; 401 | box-shadow: 2px 2px 4px #EEE; 402 | } 403 | 404 | table.docutils td, table.docutils th { 405 | border: 1px solid #888; 406 | padding: 0.25em 0.7em; 407 | } 408 | 409 | table.field-list, table.footnote { 410 | border: none; 411 | -moz-box-shadow: none; 412 | -webkit-box-shadow: none; 413 | box-shadow: none; 414 | } 415 | 416 | table.footnote { 417 | margin: 15px 0; 418 | width: 100%; 419 | border: 1px solid #EEE; 420 | background: #FDFDFD; 421 | font-size: 0.9em; 422 | } 423 | 424 | table.footnote + table.footnote { 425 | margin-top: -15px; 426 | border-top: none; 427 | } 428 | 429 | table.field-list th { 430 | padding: 0 0.8em 0 0; 431 | } 432 | 433 | table.field-list td { 434 | padding: 0; 435 | } 436 | 437 | table.field-list p { 438 | margin-bottom: 0.8em; 439 | } 440 | 441 | /* Cloned from 442 | * https://github.com/sphinx-doc/sphinx/commit/ef60dbfce09286b20b7385333d63a60321784e68 443 | */ 444 | .field-name { 445 | -moz-hyphens: manual; 446 | -ms-hyphens: manual; 447 | -webkit-hyphens: manual; 448 | hyphens: manual; 449 | } 450 | 451 | table.footnote td.label { 452 | width: .1px; 453 | padding: 0.3em 0 0.3em 0.5em; 454 | } 455 | 456 | table.footnote td { 457 | padding: 0.3em 0.5em; 458 | } 459 | 460 | dl { 461 | margin: 0; 462 | padding: 0; 463 | } 464 | 465 | dl dd { 466 | margin-left: 30px; 467 | } 468 | 469 | blockquote { 470 | margin: 0 0 0 30px; 471 | padding: 0; 472 | } 473 | 474 | ul, ol { 475 | /* Matches the 30px from the narrow-screen "li > ul" selector below */ 476 | margin: 10px 0 10px 30px; 477 | padding: 0; 478 | } 479 | 480 | pre { 481 | background: #EEE; 482 | padding: 7px 30px; 483 | margin: 15px 0px; 484 | line-height: 1.3em; 485 | } 486 | 487 | div.viewcode-block:target { 488 | background: #ffd; 489 | } 490 | 491 | dl pre, blockquote pre, li pre { 492 | margin-left: 0; 493 | padding-left: 30px; 494 | } 495 | 496 | tt, code { 497 | background-color: #ecf0f3; 498 | color: #222; 499 | /* padding: 1px 2px; */ 500 | } 501 | 502 | tt.xref, code.xref, a tt { 503 | background-color: #FBFBFB; 504 | border-bottom: 1px solid #fff; 505 | } 506 | 507 | a.reference { 508 | text-decoration: none; 509 | border-bottom: 1px dotted #004B6B; 510 | } 511 | 512 | /* Don't put an underline on images */ 513 | a.image-reference, a.image-reference:hover { 514 | border-bottom: none; 515 | } 516 | 517 | a.reference:hover { 518 | border-bottom: 1px solid #6D4100; 519 | } 520 | 521 | a.footnote-reference { 522 | text-decoration: none; 523 | font-size: 0.7em; 524 | vertical-align: top; 525 | border-bottom: 1px dotted #004B6B; 526 | } 527 | 528 | a.footnote-reference:hover { 529 | border-bottom: 1px solid #6D4100; 530 | } 531 | 532 | a:hover tt, a:hover code { 533 | background: #EEE; 534 | } 535 | 536 | 537 | @media screen and (max-width: 870px) { 538 | 539 | div.sphinxsidebar { 540 | display: none; 541 | } 542 | 543 | div.document { 544 | width: 100%; 545 | 546 | } 547 | 548 | div.documentwrapper { 549 | margin-left: 0; 550 | margin-top: 0; 551 | margin-right: 0; 552 | margin-bottom: 0; 553 | } 554 | 555 | div.bodywrapper { 556 | margin-top: 0; 557 | margin-right: 0; 558 | margin-bottom: 0; 559 | margin-left: 0; 560 | } 561 | 562 | ul { 563 | margin-left: 0; 564 | } 565 | 566 | li > ul { 567 | /* Matches the 30px from the "ul, ol" selector above */ 568 | margin-left: 30px; 569 | } 570 | 571 | .document { 572 | width: auto; 573 | } 574 | 575 | .footer { 576 | width: auto; 577 | } 578 | 579 | .bodywrapper { 580 | margin: 0; 581 | } 582 | 583 | .footer { 584 | width: auto; 585 | } 586 | 587 | .github { 588 | display: none; 589 | } 590 | 591 | 592 | 593 | } 594 | 595 | 596 | 597 | @media screen and (max-width: 875px) { 598 | 599 | body { 600 | margin: 0; 601 | padding: 20px 30px; 602 | } 603 | 604 | div.documentwrapper { 605 | float: none; 606 | background: #fff; 607 | } 608 | 609 | div.sphinxsidebar { 610 | display: block; 611 | float: none; 612 | width: 102.5%; 613 | margin: 50px -30px -20px -30px; 614 | padding: 10px 20px; 615 | background: #333; 616 | color: #FFF; 617 | } 618 | 619 | div.sphinxsidebar h3, div.sphinxsidebar h4, div.sphinxsidebar p, 620 | div.sphinxsidebar h3 a { 621 | color: #fff; 622 | } 623 | 624 | div.sphinxsidebar a { 625 | color: #AAA; 626 | } 627 | 628 | div.sphinxsidebar p.logo { 629 | display: none; 630 | } 631 | 632 | div.document { 633 | width: 100%; 634 | margin: 0; 635 | } 636 | 637 | div.footer { 638 | display: none; 639 | } 640 | 641 | div.bodywrapper { 642 | margin: 0; 643 | } 644 | 645 | div.body { 646 | min-height: 0; 647 | padding: 0; 648 | } 649 | 650 | .rtd_doc_footer { 651 | display: none; 652 | } 653 | 654 | .document { 655 | width: auto; 656 | } 657 | 658 | .footer { 659 | width: auto; 660 | } 661 | 662 | .footer { 663 | width: auto; 664 | } 665 | 666 | .github { 667 | display: none; 668 | } 669 | } 670 | 671 | 672 | /* misc. */ 673 | 674 | .revsys-inline { 675 | display: none!important; 676 | } 677 | 678 | /* Make nested-list/multi-paragraph items look better in Releases changelog 679 | * pages. Without this, docutils' magical list fuckery causes inconsistent 680 | * formatting between different release sub-lists. 681 | */ 682 | div#changelog > div.section > ul > li > p:only-child { 683 | margin-bottom: 0; 684 | } 685 | 686 | /* Hide fugly table cell borders in ..bibliography:: directive output */ 687 | table.docutils.citation, table.docutils.citation td, table.docutils.citation th { 688 | border: none; 689 | /* Below needed in some edge cases; if not applied, bottom shadows appear */ 690 | -moz-box-shadow: none; 691 | -webkit-box-shadow: none; 692 | box-shadow: none; 693 | } -------------------------------------------------------------------------------- /docs/_build/html/_static/basic.css: -------------------------------------------------------------------------------- 1 | /* 2 | * basic.css 3 | * ~~~~~~~~~ 4 | * 5 | * Sphinx stylesheet -- basic theme. 6 | * 7 | * :copyright: Copyright 2007-2017 by the Sphinx team, see AUTHORS. 8 | * :license: BSD, see LICENSE for details. 9 | * 10 | */ 11 | 12 | /* -- main layout ----------------------------------------------------------- */ 13 | 14 | div.clearer { 15 | clear: both; 16 | } 17 | 18 | /* -- relbar ---------------------------------------------------------------- */ 19 | 20 | div.related { 21 | width: 100%; 22 | font-size: 90%; 23 | } 24 | 25 | div.related h3 { 26 | display: none; 27 | } 28 | 29 | div.related ul { 30 | margin: 0; 31 | padding: 0 0 0 10px; 32 | list-style: none; 33 | } 34 | 35 | div.related li { 36 | display: inline; 37 | } 38 | 39 | div.related li.right { 40 | float: right; 41 | margin-right: 5px; 42 | } 43 | 44 | /* -- sidebar --------------------------------------------------------------- */ 45 | 46 | div.sphinxsidebarwrapper { 47 | padding: 10px 5px 0 10px; 48 | } 49 | 50 | div.sphinxsidebar { 51 | float: left; 52 | width: 230px; 53 | margin-left: -100%; 54 | font-size: 90%; 55 | word-wrap: break-word; 56 | overflow-wrap : break-word; 57 | } 58 | 59 | div.sphinxsidebar ul { 60 | list-style: none; 61 | } 62 | 63 | div.sphinxsidebar ul ul, 64 | div.sphinxsidebar ul.want-points { 65 | margin-left: 20px; 66 | list-style: square; 67 | } 68 | 69 | div.sphinxsidebar ul ul { 70 | margin-top: 0; 71 | margin-bottom: 0; 72 | } 73 | 74 | div.sphinxsidebar form { 75 | margin-top: 10px; 76 | } 77 | 78 | div.sphinxsidebar input { 79 | border: 1px solid #98dbcc; 80 | font-family: sans-serif; 81 | font-size: 1em; 82 | } 83 | 84 | div.sphinxsidebar #searchbox input[type="text"] { 85 | width: 170px; 86 | } 87 | 88 | img { 89 | border: 0; 90 | max-width: 100%; 91 | } 92 | 93 | /* -- search page ----------------------------------------------------------- */ 94 | 95 | ul.search { 96 | margin: 10px 0 0 20px; 97 | padding: 0; 98 | } 99 | 100 | ul.search li { 101 | padding: 5px 0 5px 20px; 102 | background-image: url(file.png); 103 | background-repeat: no-repeat; 104 | background-position: 0 7px; 105 | } 106 | 107 | ul.search li a { 108 | font-weight: bold; 109 | } 110 | 111 | ul.search li div.context { 112 | color: #888; 113 | margin: 2px 0 0 30px; 114 | text-align: left; 115 | } 116 | 117 | ul.keywordmatches li.goodmatch a { 118 | font-weight: bold; 119 | } 120 | 121 | /* -- index page ------------------------------------------------------------ */ 122 | 123 | table.contentstable { 124 | width: 90%; 125 | margin-left: auto; 126 | margin-right: auto; 127 | } 128 | 129 | table.contentstable p.biglink { 130 | line-height: 150%; 131 | } 132 | 133 | a.biglink { 134 | font-size: 1.3em; 135 | } 136 | 137 | span.linkdescr { 138 | font-style: italic; 139 | padding-top: 5px; 140 | font-size: 90%; 141 | } 142 | 143 | /* -- general index --------------------------------------------------------- */ 144 | 145 | table.indextable { 146 | width: 100%; 147 | } 148 | 149 | table.indextable td { 150 | text-align: left; 151 | vertical-align: top; 152 | } 153 | 154 | table.indextable ul { 155 | margin-top: 0; 156 | margin-bottom: 0; 157 | list-style-type: none; 158 | } 159 | 160 | table.indextable > tbody > tr > td > ul { 161 | padding-left: 0em; 162 | } 163 | 164 | table.indextable tr.pcap { 165 | height: 10px; 166 | } 167 | 168 | table.indextable tr.cap { 169 | margin-top: 10px; 170 | background-color: #f2f2f2; 171 | } 172 | 173 | img.toggler { 174 | margin-right: 3px; 175 | margin-top: 3px; 176 | cursor: pointer; 177 | } 178 | 179 | div.modindex-jumpbox { 180 | border-top: 1px solid #ddd; 181 | border-bottom: 1px solid #ddd; 182 | margin: 1em 0 1em 0; 183 | padding: 0.4em; 184 | } 185 | 186 | div.genindex-jumpbox { 187 | border-top: 1px solid #ddd; 188 | border-bottom: 1px solid #ddd; 189 | margin: 1em 0 1em 0; 190 | padding: 0.4em; 191 | } 192 | 193 | /* -- domain module index --------------------------------------------------- */ 194 | 195 | table.modindextable td { 196 | padding: 2px; 197 | border-collapse: collapse; 198 | } 199 | 200 | /* -- general body styles --------------------------------------------------- */ 201 | 202 | div.body p, div.body dd, div.body li, div.body blockquote { 203 | -moz-hyphens: auto; 204 | -ms-hyphens: auto; 205 | -webkit-hyphens: auto; 206 | hyphens: auto; 207 | } 208 | 209 | a.headerlink { 210 | visibility: hidden; 211 | } 212 | 213 | h1:hover > a.headerlink, 214 | h2:hover > a.headerlink, 215 | h3:hover > a.headerlink, 216 | h4:hover > a.headerlink, 217 | h5:hover > a.headerlink, 218 | h6:hover > a.headerlink, 219 | dt:hover > a.headerlink, 220 | caption:hover > a.headerlink, 221 | p.caption:hover > a.headerlink, 222 | div.code-block-caption:hover > a.headerlink { 223 | visibility: visible; 224 | } 225 | 226 | div.body p.caption { 227 | text-align: inherit; 228 | } 229 | 230 | div.body td { 231 | text-align: left; 232 | } 233 | 234 | .first { 235 | margin-top: 0 !important; 236 | } 237 | 238 | p.rubric { 239 | margin-top: 30px; 240 | font-weight: bold; 241 | } 242 | 243 | img.align-left, .figure.align-left, object.align-left { 244 | clear: left; 245 | float: left; 246 | margin-right: 1em; 247 | } 248 | 249 | img.align-right, .figure.align-right, object.align-right { 250 | clear: right; 251 | float: right; 252 | margin-left: 1em; 253 | } 254 | 255 | img.align-center, .figure.align-center, object.align-center { 256 | display: block; 257 | margin-left: auto; 258 | margin-right: auto; 259 | } 260 | 261 | .align-left { 262 | text-align: left; 263 | } 264 | 265 | .align-center { 266 | text-align: center; 267 | } 268 | 269 | .align-right { 270 | text-align: right; 271 | } 272 | 273 | /* -- sidebars -------------------------------------------------------------- */ 274 | 275 | div.sidebar { 276 | margin: 0 0 0.5em 1em; 277 | border: 1px solid #ddb; 278 | padding: 7px 7px 0 7px; 279 | background-color: #ffe; 280 | width: 40%; 281 | float: right; 282 | } 283 | 284 | p.sidebar-title { 285 | font-weight: bold; 286 | } 287 | 288 | /* -- topics ---------------------------------------------------------------- */ 289 | 290 | div.topic { 291 | border: 1px solid #ccc; 292 | padding: 7px 7px 0 7px; 293 | margin: 10px 0 10px 0; 294 | } 295 | 296 | p.topic-title { 297 | font-size: 1.1em; 298 | font-weight: bold; 299 | margin-top: 10px; 300 | } 301 | 302 | /* -- admonitions ----------------------------------------------------------- */ 303 | 304 | div.admonition { 305 | margin-top: 10px; 306 | margin-bottom: 10px; 307 | padding: 7px; 308 | } 309 | 310 | div.admonition dt { 311 | font-weight: bold; 312 | } 313 | 314 | div.admonition dl { 315 | margin-bottom: 0; 316 | } 317 | 318 | p.admonition-title { 319 | margin: 0px 10px 5px 0px; 320 | font-weight: bold; 321 | } 322 | 323 | div.body p.centered { 324 | text-align: center; 325 | margin-top: 25px; 326 | } 327 | 328 | /* -- tables ---------------------------------------------------------------- */ 329 | 330 | table.docutils { 331 | border: 0; 332 | border-collapse: collapse; 333 | } 334 | 335 | table caption span.caption-number { 336 | font-style: italic; 337 | } 338 | 339 | table caption span.caption-text { 340 | } 341 | 342 | table.docutils td, table.docutils th { 343 | padding: 1px 8px 1px 5px; 344 | border-top: 0; 345 | border-left: 0; 346 | border-right: 0; 347 | border-bottom: 1px solid #aaa; 348 | } 349 | 350 | table.footnote td, table.footnote th { 351 | border: 0 !important; 352 | } 353 | 354 | th { 355 | text-align: left; 356 | padding-right: 5px; 357 | } 358 | 359 | table.citation { 360 | border-left: solid 1px gray; 361 | margin-left: 1px; 362 | } 363 | 364 | table.citation td { 365 | border-bottom: none; 366 | } 367 | 368 | /* -- figures --------------------------------------------------------------- */ 369 | 370 | div.figure { 371 | margin: 0.5em; 372 | padding: 0.5em; 373 | } 374 | 375 | div.figure p.caption { 376 | padding: 0.3em; 377 | } 378 | 379 | div.figure p.caption span.caption-number { 380 | font-style: italic; 381 | } 382 | 383 | div.figure p.caption span.caption-text { 384 | } 385 | 386 | /* -- field list styles ----------------------------------------------------- */ 387 | 388 | table.field-list td, table.field-list th { 389 | border: 0 !important; 390 | } 391 | 392 | .field-list ul { 393 | margin: 0; 394 | padding-left: 1em; 395 | } 396 | 397 | .field-list p { 398 | margin: 0; 399 | } 400 | 401 | /* -- other body styles ----------------------------------------------------- */ 402 | 403 | ol.arabic { 404 | list-style: decimal; 405 | } 406 | 407 | ol.loweralpha { 408 | list-style: lower-alpha; 409 | } 410 | 411 | ol.upperalpha { 412 | list-style: upper-alpha; 413 | } 414 | 415 | ol.lowerroman { 416 | list-style: lower-roman; 417 | } 418 | 419 | ol.upperroman { 420 | list-style: upper-roman; 421 | } 422 | 423 | dl { 424 | margin-bottom: 15px; 425 | } 426 | 427 | dd p { 428 | margin-top: 0px; 429 | } 430 | 431 | dd ul, dd table { 432 | margin-bottom: 10px; 433 | } 434 | 435 | dd { 436 | margin-top: 3px; 437 | margin-bottom: 10px; 438 | margin-left: 30px; 439 | } 440 | 441 | dt:target, .highlighted { 442 | background-color: #fbe54e; 443 | } 444 | 445 | dl.glossary dt { 446 | font-weight: bold; 447 | font-size: 1.1em; 448 | } 449 | 450 | .optional { 451 | font-size: 1.3em; 452 | } 453 | 454 | .sig-paren { 455 | font-size: larger; 456 | } 457 | 458 | .versionmodified { 459 | font-style: italic; 460 | } 461 | 462 | .system-message { 463 | background-color: #fda; 464 | padding: 5px; 465 | border: 3px solid red; 466 | } 467 | 468 | .footnote:target { 469 | background-color: #ffa; 470 | } 471 | 472 | .line-block { 473 | display: block; 474 | margin-top: 1em; 475 | margin-bottom: 1em; 476 | } 477 | 478 | .line-block .line-block { 479 | margin-top: 0; 480 | margin-bottom: 0; 481 | margin-left: 1.5em; 482 | } 483 | 484 | .guilabel, .menuselection { 485 | font-family: sans-serif; 486 | } 487 | 488 | .accelerator { 489 | text-decoration: underline; 490 | } 491 | 492 | .classifier { 493 | font-style: oblique; 494 | } 495 | 496 | abbr, acronym { 497 | border-bottom: dotted 1px; 498 | cursor: help; 499 | } 500 | 501 | /* -- code displays --------------------------------------------------------- */ 502 | 503 | pre { 504 | overflow: auto; 505 | overflow-y: hidden; /* fixes display issues on Chrome browsers */ 506 | } 507 | 508 | span.pre { 509 | -moz-hyphens: none; 510 | -ms-hyphens: none; 511 | -webkit-hyphens: none; 512 | hyphens: none; 513 | } 514 | 515 | td.linenos pre { 516 | padding: 5px 0px; 517 | border: 0; 518 | background-color: transparent; 519 | color: #aaa; 520 | } 521 | 522 | table.highlighttable { 523 | margin-left: 0.5em; 524 | } 525 | 526 | table.highlighttable td { 527 | padding: 0 0.5em 0 0.5em; 528 | } 529 | 530 | div.code-block-caption { 531 | padding: 2px 5px; 532 | font-size: small; 533 | } 534 | 535 | div.code-block-caption code { 536 | background-color: transparent; 537 | } 538 | 539 | div.code-block-caption + div > div.highlight > pre { 540 | margin-top: 0; 541 | } 542 | 543 | div.code-block-caption span.caption-number { 544 | padding: 0.1em 0.3em; 545 | font-style: italic; 546 | } 547 | 548 | div.code-block-caption span.caption-text { 549 | } 550 | 551 | div.literal-block-wrapper { 552 | padding: 1em 1em 0; 553 | } 554 | 555 | div.literal-block-wrapper div.highlight { 556 | margin: 0; 557 | } 558 | 559 | code.descname { 560 | background-color: transparent; 561 | font-weight: bold; 562 | font-size: 1.2em; 563 | } 564 | 565 | code.descclassname { 566 | background-color: transparent; 567 | } 568 | 569 | code.xref, a code { 570 | background-color: transparent; 571 | font-weight: bold; 572 | } 573 | 574 | h1 code, h2 code, h3 code, h4 code, h5 code, h6 code { 575 | background-color: transparent; 576 | } 577 | 578 | .viewcode-link { 579 | float: right; 580 | } 581 | 582 | .viewcode-back { 583 | float: right; 584 | font-family: sans-serif; 585 | } 586 | 587 | div.viewcode-block:target { 588 | margin: -1px -10px; 589 | padding: 0 10px; 590 | } 591 | 592 | /* -- math display ---------------------------------------------------------- */ 593 | 594 | img.math { 595 | vertical-align: middle; 596 | } 597 | 598 | div.body div.math p { 599 | text-align: center; 600 | } 601 | 602 | span.eqno { 603 | float: right; 604 | } 605 | 606 | span.eqno a.headerlink { 607 | position: relative; 608 | left: 0px; 609 | z-index: 1; 610 | } 611 | 612 | div.math:hover a.headerlink { 613 | visibility: visible; 614 | } 615 | 616 | /* -- printout stylesheet --------------------------------------------------- */ 617 | 618 | @media print { 619 | div.document, 620 | div.documentwrapper, 621 | div.bodywrapper { 622 | margin: 0 !important; 623 | width: 100%; 624 | } 625 | 626 | div.sphinxsidebar, 627 | div.related, 628 | div.footer, 629 | #top-link { 630 | display: none; 631 | } 632 | } -------------------------------------------------------------------------------- /docs/_build/html/_static/comment-bright.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xnmp/lambdaquery/4ede46cb8518ff393898ffaf12cebe530dc90627/docs/_build/html/_static/comment-bright.png -------------------------------------------------------------------------------- /docs/_build/html/_static/comment-close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xnmp/lambdaquery/4ede46cb8518ff393898ffaf12cebe530dc90627/docs/_build/html/_static/comment-close.png -------------------------------------------------------------------------------- /docs/_build/html/_static/comment.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xnmp/lambdaquery/4ede46cb8518ff393898ffaf12cebe530dc90627/docs/_build/html/_static/comment.png -------------------------------------------------------------------------------- /docs/_build/html/_static/custom.css: -------------------------------------------------------------------------------- 1 | /* This file intentionally left blank. */ 2 | -------------------------------------------------------------------------------- /docs/_build/html/_static/darkmetal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xnmp/lambdaquery/4ede46cb8518ff393898ffaf12cebe530dc90627/docs/_build/html/_static/darkmetal.png -------------------------------------------------------------------------------- /docs/_build/html/_static/doctools.js: -------------------------------------------------------------------------------- 1 | /* 2 | * doctools.js 3 | * ~~~~~~~~~~~ 4 | * 5 | * Sphinx JavaScript utilities for all documentation. 6 | * 7 | * :copyright: Copyright 2007-2017 by the Sphinx team, see AUTHORS. 8 | * :license: BSD, see LICENSE for details. 9 | * 10 | */ 11 | 12 | /** 13 | * select a different prefix for underscore 14 | */ 15 | $u = _.noConflict(); 16 | 17 | /** 18 | * make the code below compatible with browsers without 19 | * an installed firebug like debugger 20 | if (!window.console || !console.firebug) { 21 | var names = ["log", "debug", "info", "warn", "error", "assert", "dir", 22 | "dirxml", "group", "groupEnd", "time", "timeEnd", "count", "trace", 23 | "profile", "profileEnd"]; 24 | window.console = {}; 25 | for (var i = 0; i < names.length; ++i) 26 | window.console[names[i]] = function() {}; 27 | } 28 | */ 29 | 30 | /** 31 | * small helper function to urldecode strings 32 | */ 33 | jQuery.urldecode = function(x) { 34 | return decodeURIComponent(x).replace(/\+/g, ' '); 35 | }; 36 | 37 | /** 38 | * small helper function to urlencode strings 39 | */ 40 | jQuery.urlencode = encodeURIComponent; 41 | 42 | /** 43 | * This function returns the parsed url parameters of the 44 | * current request. Multiple values per key are supported, 45 | * it will always return arrays of strings for the value parts. 46 | */ 47 | jQuery.getQueryParameters = function(s) { 48 | if (typeof s == 'undefined') 49 | s = document.location.search; 50 | var parts = s.substr(s.indexOf('?') + 1).split('&'); 51 | var result = {}; 52 | for (var i = 0; i < parts.length; i++) { 53 | var tmp = parts[i].split('=', 2); 54 | var key = jQuery.urldecode(tmp[0]); 55 | var value = jQuery.urldecode(tmp[1]); 56 | if (key in result) 57 | result[key].push(value); 58 | else 59 | result[key] = [value]; 60 | } 61 | return result; 62 | }; 63 | 64 | /** 65 | * highlight a given string on a jquery object by wrapping it in 66 | * span elements with the given class name. 67 | */ 68 | jQuery.fn.highlightText = function(text, className) { 69 | function highlight(node) { 70 | if (node.nodeType == 3) { 71 | var val = node.nodeValue; 72 | var pos = val.toLowerCase().indexOf(text); 73 | if (pos >= 0 && !jQuery(node.parentNode).hasClass(className)) { 74 | var span = document.createElement("span"); 75 | span.className = className; 76 | span.appendChild(document.createTextNode(val.substr(pos, text.length))); 77 | node.parentNode.insertBefore(span, node.parentNode.insertBefore( 78 | document.createTextNode(val.substr(pos + text.length)), 79 | node.nextSibling)); 80 | node.nodeValue = val.substr(0, pos); 81 | } 82 | } 83 | else if (!jQuery(node).is("button, select, textarea")) { 84 | jQuery.each(node.childNodes, function() { 85 | highlight(this); 86 | }); 87 | } 88 | } 89 | return this.each(function() { 90 | highlight(this); 91 | }); 92 | }; 93 | 94 | /* 95 | * backward compatibility for jQuery.browser 96 | * This will be supported until firefox bug is fixed. 97 | */ 98 | if (!jQuery.browser) { 99 | jQuery.uaMatch = function(ua) { 100 | ua = ua.toLowerCase(); 101 | 102 | var match = /(chrome)[ \/]([\w.]+)/.exec(ua) || 103 | /(webkit)[ \/]([\w.]+)/.exec(ua) || 104 | /(opera)(?:.*version|)[ \/]([\w.]+)/.exec(ua) || 105 | /(msie) ([\w.]+)/.exec(ua) || 106 | ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua) || 107 | []; 108 | 109 | return { 110 | browser: match[ 1 ] || "", 111 | version: match[ 2 ] || "0" 112 | }; 113 | }; 114 | jQuery.browser = {}; 115 | jQuery.browser[jQuery.uaMatch(navigator.userAgent).browser] = true; 116 | } 117 | 118 | /** 119 | * Small JavaScript module for the documentation. 120 | */ 121 | var Documentation = { 122 | 123 | init : function() { 124 | this.fixFirefoxAnchorBug(); 125 | this.highlightSearchWords(); 126 | this.initIndexTable(); 127 | 128 | }, 129 | 130 | /** 131 | * i18n support 132 | */ 133 | TRANSLATIONS : {}, 134 | PLURAL_EXPR : function(n) { return n == 1 ? 0 : 1; }, 135 | LOCALE : 'unknown', 136 | 137 | // gettext and ngettext don't access this so that the functions 138 | // can safely bound to a different name (_ = Documentation.gettext) 139 | gettext : function(string) { 140 | var translated = Documentation.TRANSLATIONS[string]; 141 | if (typeof translated == 'undefined') 142 | return string; 143 | return (typeof translated == 'string') ? translated : translated[0]; 144 | }, 145 | 146 | ngettext : function(singular, plural, n) { 147 | var translated = Documentation.TRANSLATIONS[singular]; 148 | if (typeof translated == 'undefined') 149 | return (n == 1) ? singular : plural; 150 | return translated[Documentation.PLURALEXPR(n)]; 151 | }, 152 | 153 | addTranslations : function(catalog) { 154 | for (var key in catalog.messages) 155 | this.TRANSLATIONS[key] = catalog.messages[key]; 156 | this.PLURAL_EXPR = new Function('n', 'return +(' + catalog.plural_expr + ')'); 157 | this.LOCALE = catalog.locale; 158 | }, 159 | 160 | /** 161 | * add context elements like header anchor links 162 | */ 163 | addContextElements : function() { 164 | $('div[id] > :header:first').each(function() { 165 | $('\u00B6'). 166 | attr('href', '#' + this.id). 167 | attr('title', _('Permalink to this headline')). 168 | appendTo(this); 169 | }); 170 | $('dt[id]').each(function() { 171 | $('\u00B6'). 172 | attr('href', '#' + this.id). 173 | attr('title', _('Permalink to this definition')). 174 | appendTo(this); 175 | }); 176 | }, 177 | 178 | /** 179 | * workaround a firefox stupidity 180 | * see: https://bugzilla.mozilla.org/show_bug.cgi?id=645075 181 | */ 182 | fixFirefoxAnchorBug : function() { 183 | if (document.location.hash) 184 | window.setTimeout(function() { 185 | document.location.href += ''; 186 | }, 10); 187 | }, 188 | 189 | /** 190 | * highlight the search words provided in the url in the text 191 | */ 192 | highlightSearchWords : function() { 193 | var params = $.getQueryParameters(); 194 | var terms = (params.highlight) ? params.highlight[0].split(/\s+/) : []; 195 | if (terms.length) { 196 | var body = $('div.body'); 197 | if (!body.length) { 198 | body = $('body'); 199 | } 200 | window.setTimeout(function() { 201 | $.each(terms, function() { 202 | body.highlightText(this.toLowerCase(), 'highlighted'); 203 | }); 204 | }, 10); 205 | $('') 207 | .appendTo($('#searchbox')); 208 | } 209 | }, 210 | 211 | /** 212 | * init the domain index toggle buttons 213 | */ 214 | initIndexTable : function() { 215 | var togglers = $('img.toggler').click(function() { 216 | var src = $(this).attr('src'); 217 | var idnum = $(this).attr('id').substr(7); 218 | $('tr.cg-' + idnum).toggle(); 219 | if (src.substr(-9) == 'minus.png') 220 | $(this).attr('src', src.substr(0, src.length-9) + 'plus.png'); 221 | else 222 | $(this).attr('src', src.substr(0, src.length-8) + 'minus.png'); 223 | }).css('display', ''); 224 | if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) { 225 | togglers.click(); 226 | } 227 | }, 228 | 229 | /** 230 | * helper function to hide the search marks again 231 | */ 232 | hideSearchWords : function() { 233 | $('#searchbox .highlight-link').fadeOut(300); 234 | $('span.highlighted').removeClass('highlighted'); 235 | }, 236 | 237 | /** 238 | * make the url absolute 239 | */ 240 | makeURL : function(relativeURL) { 241 | return DOCUMENTATION_OPTIONS.URL_ROOT + '/' + relativeURL; 242 | }, 243 | 244 | /** 245 | * get the current relative url 246 | */ 247 | getCurrentURL : function() { 248 | var path = document.location.pathname; 249 | var parts = path.split(/\//); 250 | $.each(DOCUMENTATION_OPTIONS.URL_ROOT.split(/\//), function() { 251 | if (this == '..') 252 | parts.pop(); 253 | }); 254 | var url = parts.join('/'); 255 | return path.substring(url.lastIndexOf('/') + 1, path.length - 1); 256 | }, 257 | 258 | initOnKeyListeners: function() { 259 | $(document).keyup(function(event) { 260 | var activeElementType = document.activeElement.tagName; 261 | // don't navigate when in search box or textarea 262 | if (activeElementType !== 'TEXTAREA' && activeElementType !== 'INPUT' && activeElementType !== 'SELECT') { 263 | switch (event.keyCode) { 264 | case 37: // left 265 | var prevHref = $('link[rel="prev"]').prop('href'); 266 | if (prevHref) { 267 | window.location.href = prevHref; 268 | return false; 269 | } 270 | case 39: // right 271 | var nextHref = $('link[rel="next"]').prop('href'); 272 | if (nextHref) { 273 | window.location.href = nextHref; 274 | return false; 275 | } 276 | } 277 | } 278 | }); 279 | } 280 | }; 281 | 282 | // quick alias for translations 283 | _ = Documentation.gettext; 284 | 285 | $(document).ready(function() { 286 | Documentation.init(); 287 | }); -------------------------------------------------------------------------------- /docs/_build/html/_static/down-pressed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xnmp/lambdaquery/4ede46cb8518ff393898ffaf12cebe530dc90627/docs/_build/html/_static/down-pressed.png -------------------------------------------------------------------------------- /docs/_build/html/_static/down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xnmp/lambdaquery/4ede46cb8518ff393898ffaf12cebe530dc90627/docs/_build/html/_static/down.png -------------------------------------------------------------------------------- /docs/_build/html/_static/file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xnmp/lambdaquery/4ede46cb8518ff393898ffaf12cebe530dc90627/docs/_build/html/_static/file.png -------------------------------------------------------------------------------- /docs/_build/html/_static/headerbg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xnmp/lambdaquery/4ede46cb8518ff393898ffaf12cebe530dc90627/docs/_build/html/_static/headerbg.png -------------------------------------------------------------------------------- /docs/_build/html/_static/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xnmp/lambdaquery/4ede46cb8518ff393898ffaf12cebe530dc90627/docs/_build/html/_static/logo.png -------------------------------------------------------------------------------- /docs/_build/html/_static/metal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xnmp/lambdaquery/4ede46cb8518ff393898ffaf12cebe530dc90627/docs/_build/html/_static/metal.png -------------------------------------------------------------------------------- /docs/_build/html/_static/minus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xnmp/lambdaquery/4ede46cb8518ff393898ffaf12cebe530dc90627/docs/_build/html/_static/minus.png -------------------------------------------------------------------------------- /docs/_build/html/_static/navigation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xnmp/lambdaquery/4ede46cb8518ff393898ffaf12cebe530dc90627/docs/_build/html/_static/navigation.png -------------------------------------------------------------------------------- /docs/_build/html/_static/plus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xnmp/lambdaquery/4ede46cb8518ff393898ffaf12cebe530dc90627/docs/_build/html/_static/plus.png -------------------------------------------------------------------------------- /docs/_build/html/_static/print.css: -------------------------------------------------------------------------------- 1 | @media print { 2 | div.header, div.relnav, #toc { display: none; } 3 | #contentwrapper { padding: 0; margin: 0; border: none; } 4 | body { color: black; background-color: white; } 5 | div.footer { border-top: 1px solid #888; color: #888; margin-top: 1cm; } 6 | div.footer a { text-decoration: none; } 7 | } 8 | -------------------------------------------------------------------------------- /docs/_build/html/_static/pygments.css: -------------------------------------------------------------------------------- 1 | .highlight .hll { background-color: #ffffcc } 2 | .highlight { background: #eeffcc; } 3 | .highlight .c { color: #408090; font-style: italic } /* Comment */ 4 | .highlight .err { border: 1px solid #FF0000 } /* Error */ 5 | .highlight .k { color: #007020; font-weight: bold } /* Keyword */ 6 | .highlight .o { color: #666666 } /* Operator */ 7 | .highlight .ch { color: #408090; font-style: italic } /* Comment.Hashbang */ 8 | .highlight .cm { color: #408090; font-style: italic } /* Comment.Multiline */ 9 | .highlight .cp { color: #007020 } /* Comment.Preproc */ 10 | .highlight .cpf { color: #408090; font-style: italic } /* Comment.PreprocFile */ 11 | .highlight .c1 { color: #408090; font-style: italic } /* Comment.Single */ 12 | .highlight .cs { color: #408090; background-color: #fff0f0 } /* Comment.Special */ 13 | .highlight .gd { color: #A00000 } /* Generic.Deleted */ 14 | .highlight .ge { font-style: italic } /* Generic.Emph */ 15 | .highlight .gr { color: #FF0000 } /* Generic.Error */ 16 | .highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */ 17 | .highlight .gi { color: #00A000 } /* Generic.Inserted */ 18 | .highlight .go { color: #333333 } /* Generic.Output */ 19 | .highlight .gp { color: #c65d09; font-weight: bold } /* Generic.Prompt */ 20 | .highlight .gs { font-weight: bold } /* Generic.Strong */ 21 | .highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ 22 | .highlight .gt { color: #0044DD } /* Generic.Traceback */ 23 | .highlight .kc { color: #007020; font-weight: bold } /* Keyword.Constant */ 24 | .highlight .kd { color: #007020; font-weight: bold } /* Keyword.Declaration */ 25 | .highlight .kn { color: #007020; font-weight: bold } /* Keyword.Namespace */ 26 | .highlight .kp { color: #007020 } /* Keyword.Pseudo */ 27 | .highlight .kr { color: #007020; font-weight: bold } /* Keyword.Reserved */ 28 | .highlight .kt { color: #902000 } /* Keyword.Type */ 29 | .highlight .m { color: #208050 } /* Literal.Number */ 30 | .highlight .s { color: #4070a0 } /* Literal.String */ 31 | .highlight .na { color: #4070a0 } /* Name.Attribute */ 32 | .highlight .nb { color: #007020 } /* Name.Builtin */ 33 | .highlight .nc { color: #0e84b5; font-weight: bold } /* Name.Class */ 34 | .highlight .no { color: #60add5 } /* Name.Constant */ 35 | .highlight .nd { color: #555555; font-weight: bold } /* Name.Decorator */ 36 | .highlight .ni { color: #d55537; font-weight: bold } /* Name.Entity */ 37 | .highlight .ne { color: #007020 } /* Name.Exception */ 38 | .highlight .nf { color: #06287e } /* Name.Function */ 39 | .highlight .nl { color: #002070; font-weight: bold } /* Name.Label */ 40 | .highlight .nn { color: #0e84b5; font-weight: bold } /* Name.Namespace */ 41 | .highlight .nt { color: #062873; font-weight: bold } /* Name.Tag */ 42 | .highlight .nv { color: #bb60d5 } /* Name.Variable */ 43 | .highlight .ow { color: #007020; font-weight: bold } /* Operator.Word */ 44 | .highlight .w { color: #bbbbbb } /* Text.Whitespace */ 45 | .highlight .mb { color: #208050 } /* Literal.Number.Bin */ 46 | .highlight .mf { color: #208050 } /* Literal.Number.Float */ 47 | .highlight .mh { color: #208050 } /* Literal.Number.Hex */ 48 | .highlight .mi { color: #208050 } /* Literal.Number.Integer */ 49 | .highlight .mo { color: #208050 } /* Literal.Number.Oct */ 50 | .highlight .sa { color: #4070a0 } /* Literal.String.Affix */ 51 | .highlight .sb { color: #4070a0 } /* Literal.String.Backtick */ 52 | .highlight .sc { color: #4070a0 } /* Literal.String.Char */ 53 | .highlight .dl { color: #4070a0 } /* Literal.String.Delimiter */ 54 | .highlight .sd { color: #4070a0; font-style: italic } /* Literal.String.Doc */ 55 | .highlight .s2 { color: #4070a0 } /* Literal.String.Double */ 56 | .highlight .se { color: #4070a0; font-weight: bold } /* Literal.String.Escape */ 57 | .highlight .sh { color: #4070a0 } /* Literal.String.Heredoc */ 58 | .highlight .si { color: #70a0d0; font-style: italic } /* Literal.String.Interpol */ 59 | .highlight .sx { color: #c65d09 } /* Literal.String.Other */ 60 | .highlight .sr { color: #235388 } /* Literal.String.Regex */ 61 | .highlight .s1 { color: #4070a0 } /* Literal.String.Single */ 62 | .highlight .ss { color: #517918 } /* Literal.String.Symbol */ 63 | .highlight .bp { color: #007020 } /* Name.Builtin.Pseudo */ 64 | .highlight .fm { color: #06287e } /* Name.Function.Magic */ 65 | .highlight .vc { color: #bb60d5 } /* Name.Variable.Class */ 66 | .highlight .vg { color: #bb60d5 } /* Name.Variable.Global */ 67 | .highlight .vi { color: #bb60d5 } /* Name.Variable.Instance */ 68 | .highlight .vm { color: #bb60d5 } /* Name.Variable.Magic */ 69 | .highlight .il { color: #208050 } /* Literal.Number.Integer.Long */ -------------------------------------------------------------------------------- /docs/_build/html/_static/scrolls.css: -------------------------------------------------------------------------------- 1 | /* 2 | * scrolls.css_t 3 | * ~~~~~~~~~~~~~ 4 | * 5 | * Sphinx stylesheet -- scrolls theme. 6 | * 7 | * :copyright: Copyright 2007-2017 by the Sphinx team, see AUTHORS. 8 | * :license: BSD, see LICENSE for details. 9 | * 10 | */ 11 | 12 | body { 13 | background-color: #222; 14 | margin: 0; 15 | padding: 0; 16 | font-family: 'Georgia', serif; 17 | font-size: 15px; 18 | color: #eee; 19 | } 20 | 21 | div.footer { 22 | border-top: 1px solid #111; 23 | padding: 8px; 24 | font-size: 11px; 25 | text-align: center; 26 | letter-spacing: 0.5px; 27 | } 28 | 29 | div.footer a { 30 | color: #eee; 31 | } 32 | 33 | div.header { 34 | margin: 0 -15px 0 -15px; 35 | background: url(headerbg.png) repeat-x; 36 | border-top: 6px solid #1752b4; 37 | } 38 | 39 | div.relnav { 40 | border-bottom: 1px solid #111; 41 | background: url(navigation.png); 42 | margin: 0 -15px 0 -15px; 43 | padding: 2px 20px 0 28px; 44 | line-height: 25px; 45 | color: #aaa; 46 | font-size: 12px; 47 | text-align: center; 48 | } 49 | 50 | div.relnav a { 51 | color: #eee; 52 | font-weight: bold; 53 | text-decoration: none; 54 | } 55 | 56 | div.relnav a:hover { 57 | text-decoration: underline; 58 | } 59 | 60 | #content { 61 | background-color: white; 62 | color: #111; 63 | border-bottom: 1px solid black; 64 | background: url(watermark.png) center 0; 65 | padding: 0 15px 0 15px; 66 | margin: 0; 67 | } 68 | 69 | h1 { 70 | margin: 0; 71 | padding: 15px 0 0 0; 72 | } 73 | 74 | h1.heading { 75 | margin: 0; 76 | padding: 0; 77 | height: 80px; 78 | } 79 | 80 | h1.heading:hover { 81 | background: #222; 82 | } 83 | 84 | h1.heading a { 85 | background: url(logo.png) no-repeat center 0; 86 | display: block; 87 | width: 100%; 88 | height: 80px; 89 | } 90 | 91 | h1.heading a:focus { 92 | -moz-outline: none; 93 | outline: none; 94 | } 95 | 96 | h1.heading span { 97 | display: none; 98 | } 99 | 100 | #contentwrapper { 101 | max-width: 680px; 102 | padding: 0 18px 20px 18px; 103 | margin: 0 auto 0 auto; 104 | border-right: 1px solid #eee; 105 | border-left: 1px solid #eee; 106 | background: url(watermark_blur.png) center -114px; 107 | } 108 | 109 | #contentwrapper h2, 110 | #contentwrapper h2 a { 111 | color: #222; 112 | font-size: 24px; 113 | margin: 20px 0 0 0; 114 | } 115 | 116 | #contentwrapper h3, 117 | #contentwrapper h3 a { 118 | color: #0d306b; 119 | font-size: 20px; 120 | margin: 20px 0 0 0; 121 | } 122 | 123 | table.docutils { 124 | border-collapse: collapse; 125 | border: 2px solid #aaa; 126 | margin: 0.5em 1.5em 0.5em 1.5em; 127 | } 128 | 129 | table.docutils td { 130 | padding: 2px; 131 | border: 1px solid #ddd; 132 | } 133 | 134 | p, li, dd, dt, blockquote { 135 | color: #333; 136 | } 137 | 138 | blockquote { 139 | margin: 10px 0 10px 20px; 140 | } 141 | 142 | p { 143 | line-height: 20px; 144 | margin-bottom: 0; 145 | margin-top: 10px; 146 | } 147 | 148 | hr { 149 | border-top: 1px solid #ccc; 150 | border-bottom: 0; 151 | border-right: 0; 152 | border-left: 0; 153 | margin-bottom: 10px; 154 | margin-top: 20px; 155 | } 156 | 157 | dl { 158 | margin-left: 10px; 159 | } 160 | 161 | li, dt { 162 | margin-top: 5px; 163 | } 164 | 165 | dt { 166 | font-weight: bold; 167 | color: #000; 168 | } 169 | 170 | dd { 171 | margin-top: 10px; 172 | line-height: 20px; 173 | } 174 | 175 | th { 176 | text-align: left; 177 | padding: 3px; 178 | background-color: #f2f2f2; 179 | } 180 | 181 | a { 182 | color: #1752b4; 183 | } 184 | 185 | a:hover { 186 | color: #444; 187 | } 188 | 189 | pre { 190 | background: #ededed url(metal.png); 191 | border-top: 1px solid #ccc; 192 | border-bottom: 1px solid #ccc; 193 | padding: 5px; 194 | font-size: 13px; 195 | font-family: 'Bitstream Vera Sans Mono', 'Monaco', monospace; 196 | } 197 | 198 | code { 199 | font-size: 13px; 200 | font-family: 'Bitstream Vera Sans Mono', 'Monaco', monospace; 201 | color: black; 202 | padding: 1px 2px 1px 2px; 203 | background-color: #fafafa; 204 | border-bottom: 1px solid #eee; 205 | } 206 | 207 | div.code-block-caption { 208 | color: #efefef; 209 | background-color: #888; 210 | } 211 | 212 | div.code-block-caption span.caption-number { 213 | padding: 0.1em 0.3em; 214 | font-style: italic; 215 | } 216 | 217 | div.code-block-caption span.caption-text { 218 | } 219 | 220 | div.literal-block-wrapper { 221 | padding: 1em 1em 0; 222 | } 223 | 224 | div.literal-block-wrapper pre { 225 | margin: 0; 226 | } 227 | 228 | a.reference:hover code { 229 | border-bottom-color: #aaa; 230 | } 231 | 232 | cite { 233 | /* abusing , it's generated by ReST for `x` */ 234 | font-size: 13px; 235 | font-family: 'Bitstream Vera Sans Mono', 'Monaco', monospace; 236 | font-weight: bold; 237 | font-style: normal; 238 | } 239 | 240 | div.admonition { 241 | margin: 10px 0 10px 0; 242 | padding: 10px; 243 | border: 1px solid #ccc; 244 | } 245 | 246 | div.admonition p.admonition-title { 247 | background-color: #28437f; 248 | color: white; 249 | margin: -10px -10px 10px -10px; 250 | padding: 4px 10px 4px 10px; 251 | font-weight: bold; 252 | font-size: 15px; 253 | } 254 | 255 | div.admonition p.admonition-title a { 256 | color: white!important; 257 | } 258 | 259 | a.headerlink { 260 | color: #B4B4B4!important; 261 | font-size: 0.8em; 262 | padding: 0 4px 0 4px; 263 | text-decoration: none!important; 264 | visibility: hidden; 265 | } 266 | 267 | h1:hover > a.headerlink, 268 | h2:hover > a.headerlink, 269 | h3:hover > a.headerlink, 270 | h4:hover > a.headerlink, 271 | h5:hover > a.headerlink, 272 | h6:hover > a.headerlink, 273 | dt:hover > a.headerlink, 274 | dt:hover > a.headerlink, 275 | caption:hover > a.headerlink, 276 | p.caption:hover > a.headerlink, 277 | div.code-block-caption:hover > a.headerlink { 278 | visibility: visible; 279 | } 280 | 281 | a.headerlink:hover { 282 | background-color: #B4B4B4; 283 | color: #F0F0F0!important; 284 | } 285 | 286 | table caption span.caption-number { 287 | font-style: italic; 288 | } 289 | 290 | table caption span.caption-text { 291 | } 292 | 293 | table.indextable { 294 | width: 100%; 295 | } 296 | 297 | table.genindextable td { 298 | vertical-align: top; 299 | width: 50%; 300 | } 301 | 302 | table.indextable ul { 303 | margin-top: 0; 304 | margin-bottom: 0; 305 | list-style-type: none; 306 | font-size: 11px; 307 | } 308 | 309 | table.indextable ul a { 310 | color: #000; 311 | } 312 | 313 | table.indextable > tbody > tr > td > ul { 314 | padding-left: 0em; 315 | } 316 | 317 | div.modindex-jumpbox { 318 | border-top: 1px solid #ddd; 319 | border-bottom: 1px solid #ddd; 320 | margin: 1em 0 1em 0; 321 | padding: 0.4em; 322 | } 323 | 324 | table.modindextable { 325 | width: 100%; 326 | border: none; 327 | } 328 | 329 | table.modindextable td { 330 | padding: 2px; 331 | border-collapse: collapse; 332 | } 333 | 334 | table.modindextable img.toggler { 335 | margin-right: 10px; 336 | } 337 | 338 | dl.function dt, 339 | dl.class dt, 340 | dl.exception dt, 341 | dl.method dt, 342 | dl.attribute dt { 343 | font-weight: normal; 344 | } 345 | 346 | dt .descname { 347 | font-weight: bold; 348 | margin-right: 4px; 349 | } 350 | 351 | dt .sig-paren { 352 | font-size: larger; 353 | } 354 | 355 | dt .descname, dt .descclassname { 356 | padding: 0; 357 | background: transparent; 358 | border-bottom: 1px solid #111; 359 | } 360 | 361 | dt .descclassname { 362 | margin-left: 2px; 363 | } 364 | 365 | dl dt big { 366 | font-size: 100%; 367 | } 368 | 369 | ul.search { 370 | margin: 10px 0 0 30px; 371 | padding: 0; 372 | } 373 | 374 | ul.search li { 375 | margin: 10px 0 0 0; 376 | padding: 0; 377 | } 378 | 379 | ul.search div.context { 380 | font-size: 12px; 381 | padding: 4px 0 0 20px; 382 | color: #888; 383 | } 384 | 385 | span.highlight { 386 | background-color: #eee; 387 | border: 1px solid #ccc; 388 | } 389 | 390 | #toc { 391 | margin: 0 -17px 0 -17px; 392 | display: none; 393 | } 394 | 395 | #toc h3 { 396 | float: right; 397 | margin: 5px 5px 0 0; 398 | padding: 0; 399 | font-size: 12px; 400 | color: #777; 401 | } 402 | 403 | #toc h3:hover { 404 | color: #333; 405 | cursor: pointer; 406 | } 407 | 408 | .expandedtoc { 409 | background: #222 url(darkmetal.png); 410 | border-bottom: 1px solid #111; 411 | outline-bottom: 1px solid #000; 412 | padding: 5px; 413 | } 414 | 415 | .expandedtoc h3 { 416 | color: #aaa; 417 | margin: 0!important; 418 | } 419 | 420 | .expandedtoc h3:hover { 421 | color: white!important; 422 | } 423 | 424 | #tod h3:hover { 425 | color: white; 426 | } 427 | 428 | #toc a { 429 | color: #ddd; 430 | text-decoration: none; 431 | } 432 | 433 | #toc a:hover { 434 | color: white; 435 | text-decoration: underline; 436 | } 437 | 438 | #toc ul { 439 | margin: 5px 0 12px 17px; 440 | padding: 0 7px 0 7px; 441 | } 442 | 443 | #toc ul ul { 444 | margin-bottom: 0; 445 | } 446 | 447 | #toc ul li { 448 | margin: 2px 0 0 0; 449 | } 450 | 451 | .line-block { 452 | display: block; 453 | margin-top: 1em; 454 | margin-bottom: 1em; 455 | } 456 | 457 | .line-block .line-block { 458 | margin-top: 0; 459 | margin-bottom: 0; 460 | margin-left: 1.5em; 461 | } 462 | 463 | .viewcode-link { 464 | float: right; 465 | } 466 | 467 | .viewcode-back { 468 | float: right; 469 | font-family: 'Georgia', serif; 470 | } 471 | 472 | div.viewcode-block:target { 473 | background-color: #f4debf; 474 | border-top: 1px solid #ac9; 475 | border-bottom: 1px solid #ac9; 476 | margin: -1px -5px; 477 | padding: 0 5px; 478 | } 479 | 480 | div.figure p.caption span.caption-number { 481 | font-style: italic; 482 | } 483 | 484 | div.figure p.caption span.caption-text { 485 | } 486 | 487 | /* math display */ 488 | 489 | div.math p { 490 | text-align: center; 491 | } 492 | 493 | span.eqno { 494 | float: right; 495 | } -------------------------------------------------------------------------------- /docs/_build/html/_static/theme_extras.js: -------------------------------------------------------------------------------- 1 | $(function() { 2 | 3 | var 4 | toc = $('#toc').show(), 5 | items = $('#toc > ul').hide(); 6 | 7 | $('#toc h3') 8 | .click(function() { 9 | if (items.is(':visible')) { 10 | items.animate({ 11 | height: 'hide', 12 | opacity: 'hide' 13 | }, 300, function() { 14 | toc.removeClass('expandedtoc'); 15 | }); 16 | } 17 | else { 18 | items.animate({ 19 | height: 'show', 20 | opacity: 'show' 21 | }, 400); 22 | toc.addClass('expandedtoc'); 23 | } 24 | }); 25 | 26 | }); 27 | -------------------------------------------------------------------------------- /docs/_build/html/_static/underscore.js: -------------------------------------------------------------------------------- 1 | // Underscore.js 1.3.1 2 | // (c) 2009-2012 Jeremy Ashkenas, DocumentCloud Inc. 3 | // Underscore is freely distributable under the MIT license. 4 | // Portions of Underscore are inspired or borrowed from Prototype, 5 | // Oliver Steele's Functional, and John Resig's Micro-Templating. 6 | // For all details and documentation: 7 | // http://documentcloud.github.com/underscore 8 | (function(){function q(a,c,d){if(a===c)return a!==0||1/a==1/c;if(a==null||c==null)return a===c;if(a._chain)a=a._wrapped;if(c._chain)c=c._wrapped;if(a.isEqual&&b.isFunction(a.isEqual))return a.isEqual(c);if(c.isEqual&&b.isFunction(c.isEqual))return c.isEqual(a);var e=l.call(a);if(e!=l.call(c))return false;switch(e){case "[object String]":return a==String(c);case "[object Number]":return a!=+a?c!=+c:a==0?1/a==1/c:a==+c;case "[object Date]":case "[object Boolean]":return+a==+c;case "[object RegExp]":return a.source== 9 | c.source&&a.global==c.global&&a.multiline==c.multiline&&a.ignoreCase==c.ignoreCase}if(typeof a!="object"||typeof c!="object")return false;for(var f=d.length;f--;)if(d[f]==a)return true;d.push(a);var f=0,g=true;if(e=="[object Array]"){if(f=a.length,g=f==c.length)for(;f--;)if(!(g=f in a==f in c&&q(a[f],c[f],d)))break}else{if("constructor"in a!="constructor"in c||a.constructor!=c.constructor)return false;for(var h in a)if(b.has(a,h)&&(f++,!(g=b.has(c,h)&&q(a[h],c[h],d))))break;if(g){for(h in c)if(b.has(c, 10 | h)&&!f--)break;g=!f}}d.pop();return g}var r=this,G=r._,n={},k=Array.prototype,o=Object.prototype,i=k.slice,H=k.unshift,l=o.toString,I=o.hasOwnProperty,w=k.forEach,x=k.map,y=k.reduce,z=k.reduceRight,A=k.filter,B=k.every,C=k.some,p=k.indexOf,D=k.lastIndexOf,o=Array.isArray,J=Object.keys,s=Function.prototype.bind,b=function(a){return new m(a)};if(typeof exports!=="undefined"){if(typeof module!=="undefined"&&module.exports)exports=module.exports=b;exports._=b}else r._=b;b.VERSION="1.3.1";var j=b.each= 11 | b.forEach=function(a,c,d){if(a!=null)if(w&&a.forEach===w)a.forEach(c,d);else if(a.length===+a.length)for(var e=0,f=a.length;e2;a== 12 | null&&(a=[]);if(y&&a.reduce===y)return e&&(c=b.bind(c,e)),f?a.reduce(c,d):a.reduce(c);j(a,function(a,b,i){f?d=c.call(e,d,a,b,i):(d=a,f=true)});if(!f)throw new TypeError("Reduce of empty array with no initial value");return d};b.reduceRight=b.foldr=function(a,c,d,e){var f=arguments.length>2;a==null&&(a=[]);if(z&&a.reduceRight===z)return e&&(c=b.bind(c,e)),f?a.reduceRight(c,d):a.reduceRight(c);var g=b.toArray(a).reverse();e&&!f&&(c=b.bind(c,e));return f?b.reduce(g,c,d,e):b.reduce(g,c)};b.find=b.detect= 13 | function(a,c,b){var e;E(a,function(a,g,h){if(c.call(b,a,g,h))return e=a,true});return e};b.filter=b.select=function(a,c,b){var e=[];if(a==null)return e;if(A&&a.filter===A)return a.filter(c,b);j(a,function(a,g,h){c.call(b,a,g,h)&&(e[e.length]=a)});return e};b.reject=function(a,c,b){var e=[];if(a==null)return e;j(a,function(a,g,h){c.call(b,a,g,h)||(e[e.length]=a)});return e};b.every=b.all=function(a,c,b){var e=true;if(a==null)return e;if(B&&a.every===B)return a.every(c,b);j(a,function(a,g,h){if(!(e= 14 | e&&c.call(b,a,g,h)))return n});return e};var E=b.some=b.any=function(a,c,d){c||(c=b.identity);var e=false;if(a==null)return e;if(C&&a.some===C)return a.some(c,d);j(a,function(a,b,h){if(e||(e=c.call(d,a,b,h)))return n});return!!e};b.include=b.contains=function(a,c){var b=false;if(a==null)return b;return p&&a.indexOf===p?a.indexOf(c)!=-1:b=E(a,function(a){return a===c})};b.invoke=function(a,c){var d=i.call(arguments,2);return b.map(a,function(a){return(b.isFunction(c)?c||a:a[c]).apply(a,d)})};b.pluck= 15 | function(a,c){return b.map(a,function(a){return a[c]})};b.max=function(a,c,d){if(!c&&b.isArray(a))return Math.max.apply(Math,a);if(!c&&b.isEmpty(a))return-Infinity;var e={computed:-Infinity};j(a,function(a,b,h){b=c?c.call(d,a,b,h):a;b>=e.computed&&(e={value:a,computed:b})});return e.value};b.min=function(a,c,d){if(!c&&b.isArray(a))return Math.min.apply(Math,a);if(!c&&b.isEmpty(a))return Infinity;var e={computed:Infinity};j(a,function(a,b,h){b=c?c.call(d,a,b,h):a;bd?1:0}),"value")};b.groupBy=function(a,c){var d={},e=b.isFunction(c)?c:function(a){return a[c]};j(a,function(a,b){var c=e(a,b);(d[c]||(d[c]=[])).push(a)});return d};b.sortedIndex=function(a, 17 | c,d){d||(d=b.identity);for(var e=0,f=a.length;e>1;d(a[g])=0})})};b.difference=function(a){var c=b.flatten(i.call(arguments,1));return b.filter(a,function(a){return!b.include(c,a)})};b.zip=function(){for(var a=i.call(arguments),c=b.max(b.pluck(a,"length")),d=Array(c),e=0;e=0;d--)b=[a[d].apply(this,b)];return b[0]}}; 24 | b.after=function(a,b){return a<=0?b():function(){if(--a<1)return b.apply(this,arguments)}};b.keys=J||function(a){if(a!==Object(a))throw new TypeError("Invalid object");var c=[],d;for(d in a)b.has(a,d)&&(c[c.length]=d);return c};b.values=function(a){return b.map(a,b.identity)};b.functions=b.methods=function(a){var c=[],d;for(d in a)b.isFunction(a[d])&&c.push(d);return c.sort()};b.extend=function(a){j(i.call(arguments,1),function(b){for(var d in b)a[d]=b[d]});return a};b.defaults=function(a){j(i.call(arguments, 25 | 1),function(b){for(var d in b)a[d]==null&&(a[d]=b[d])});return a};b.clone=function(a){return!b.isObject(a)?a:b.isArray(a)?a.slice():b.extend({},a)};b.tap=function(a,b){b(a);return a};b.isEqual=function(a,b){return q(a,b,[])};b.isEmpty=function(a){if(b.isArray(a)||b.isString(a))return a.length===0;for(var c in a)if(b.has(a,c))return false;return true};b.isElement=function(a){return!!(a&&a.nodeType==1)};b.isArray=o||function(a){return l.call(a)=="[object Array]"};b.isObject=function(a){return a===Object(a)}; 26 | b.isArguments=function(a){return l.call(a)=="[object Arguments]"};if(!b.isArguments(arguments))b.isArguments=function(a){return!(!a||!b.has(a,"callee"))};b.isFunction=function(a){return l.call(a)=="[object Function]"};b.isString=function(a){return l.call(a)=="[object String]"};b.isNumber=function(a){return l.call(a)=="[object Number]"};b.isNaN=function(a){return a!==a};b.isBoolean=function(a){return a===true||a===false||l.call(a)=="[object Boolean]"};b.isDate=function(a){return l.call(a)=="[object Date]"}; 27 | b.isRegExp=function(a){return l.call(a)=="[object RegExp]"};b.isNull=function(a){return a===null};b.isUndefined=function(a){return a===void 0};b.has=function(a,b){return I.call(a,b)};b.noConflict=function(){r._=G;return this};b.identity=function(a){return a};b.times=function(a,b,d){for(var e=0;e/g,">").replace(/"/g,""").replace(/'/g,"'").replace(/\//g,"/")};b.mixin=function(a){j(b.functions(a), 28 | function(c){K(c,b[c]=a[c])})};var L=0;b.uniqueId=function(a){var b=L++;return a?a+b:b};b.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var t=/.^/,u=function(a){return a.replace(/\\\\/g,"\\").replace(/\\'/g,"'")};b.template=function(a,c){var d=b.templateSettings,d="var __p=[],print=function(){__p.push.apply(__p,arguments);};with(obj||{}){__p.push('"+a.replace(/\\/g,"\\\\").replace(/'/g,"\\'").replace(d.escape||t,function(a,b){return"',_.escape("+ 29 | u(b)+"),'"}).replace(d.interpolate||t,function(a,b){return"',"+u(b)+",'"}).replace(d.evaluate||t,function(a,b){return"');"+u(b).replace(/[\r\n\t]/g," ")+";__p.push('"}).replace(/\r/g,"\\r").replace(/\n/g,"\\n").replace(/\t/g,"\\t")+"');}return __p.join('');",e=new Function("obj","_",d);return c?e(c,b):function(a){return e.call(this,a,b)}};b.chain=function(a){return b(a).chain()};var m=function(a){this._wrapped=a};b.prototype=m.prototype;var v=function(a,c){return c?b(a).chain():a},K=function(a,c){m.prototype[a]= 30 | function(){var a=i.call(arguments);H.call(a,this._wrapped);return v(c.apply(b,a),this._chain)}};b.mixin(b);j("pop,push,reverse,shift,sort,splice,unshift".split(","),function(a){var b=k[a];m.prototype[a]=function(){var d=this._wrapped;b.apply(d,arguments);var e=d.length;(a=="shift"||a=="splice")&&e===0&&delete d[0];return v(d,this._chain)}});j(["concat","join","slice"],function(a){var b=k[a];m.prototype[a]=function(){return v(b.apply(this._wrapped,arguments),this._chain)}});m.prototype.chain=function(){this._chain= 31 | true;return this};m.prototype.value=function(){return this._wrapped}}).call(this); 32 | -------------------------------------------------------------------------------- /docs/_build/html/_static/up-pressed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xnmp/lambdaquery/4ede46cb8518ff393898ffaf12cebe530dc90627/docs/_build/html/_static/up-pressed.png -------------------------------------------------------------------------------- /docs/_build/html/_static/up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xnmp/lambdaquery/4ede46cb8518ff393898ffaf12cebe530dc90627/docs/_build/html/_static/up.png -------------------------------------------------------------------------------- /docs/_build/html/_static/watermark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xnmp/lambdaquery/4ede46cb8518ff393898ffaf12cebe530dc90627/docs/_build/html/_static/watermark.png -------------------------------------------------------------------------------- /docs/_build/html/_static/watermark_blur.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xnmp/lambdaquery/4ede46cb8518ff393898ffaf12cebe530dc90627/docs/_build/html/_static/watermark_blur.png -------------------------------------------------------------------------------- /docs/_build/html/advanced.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Advanced — LambdaQuery 0.1 documentation 10 | 11 | 12 | 13 | 14 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 |
42 |
43 |
44 |
45 | 46 |
47 |

Advanced

48 |
49 |

Grouping By

50 |

Grouping by

51 |
52 |
53 |

Last Before

54 |
55 |
56 |

Using Kleisli and Lifted

57 |

If you wrap these you get a few benefits:

58 |
    59 |
  • The resulting columns have more readable names
  • 60 |
  • You can use the resulting functions as methods
  • 61 |
  • They add on the group bys of their source.
  • 62 |
63 |
64 |
65 |
66 |

How it Works

67 |

Basically the internal representation of a query is as a graph. Query combinators are just transformations on this graph. Before generating the SQL, LambdaQuery performs a reduction algorithm on this graph, and then the reduced graph is compiled to SQL. To see the SQL of the unreduced graph, use the reduce=False option for the Query.sql method.

68 |
69 | 70 | 71 |
72 |
73 |
74 | 114 |
115 |
116 | 127 | 128 | 129 | 130 | 131 | 132 | -------------------------------------------------------------------------------- /docs/_build/html/faq.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | FAQ — LambdaQuery 0.1 documentation 10 | 11 | 12 | 13 | 14 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 |
41 |
42 |
43 |
44 | 45 |
46 |

FAQ

47 |

Coming soon!

48 |

Please raise an issue on Github here.

49 |
50 | 51 | 52 |
53 |
54 |
55 | 83 |
84 |
85 | 96 | 97 | 98 | 99 | 100 | 101 | -------------------------------------------------------------------------------- /docs/_build/html/genindex.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Index — LambdaQuery 0.1 documentation 11 | 12 | 13 | 14 | 15 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 |
41 |
42 |
43 |
44 | 45 | 46 |

Index

47 | 48 |
49 | 50 |
51 | 52 | 53 |
54 |
55 |
56 | 79 |
80 |
81 | 89 | 90 | 91 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /docs/_build/html/index.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Welcome to λQuery! — LambdaQuery 0.1 documentation 10 | 11 | 12 | 13 | 14 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 |
41 |
42 |
43 |
44 | 45 |
46 |

Welcome to λQuery!

47 |
48 |

Contents

49 | 88 |
89 |

This is a library that enables one to write composable SQL in pure python. It is heavily functional, enabling one to chain query combinators in incidentally very similar to the Rabbit query language.

90 |

The question that we solve - why is writing SQL so hard? How is it that something describable in two sentences in English becomes a monster of a query? How does one not become lost navigating the scoping rules and the throwaway names that become rampant when you have more than one layer of aggregation? LambdaQuery is the answer.

91 |

The main goals of LambdaQuery are to be a query API that:

92 |
    93 |
  • Removes as much syntactic noise as possible.

    94 |
    95 |
      96 |
    • Joining by foreign keys automatically.
    • 97 |
    • Handles all of the red tape in SQL.
    • 98 |
    • Automatically assigns the throaway names to subqueries and columns of subqueries.
    • 99 |
    • Knows which tables should be left joined and handles the null cases.
    • 100 |
    • Knows which relationships are one to one, and choosing the group bys appropriately to preserve structure of the rows.
    • 101 |
    102 |
    103 |
  • 104 |
  • Syntax that is very similar to manipulating in-memory Python lists.

    105 |
    106 |
      107 |
    • Being able to work with particular elements of a list or the list as a whole.
    • 108 |
    • Having CLEAN syntax just like actual Python.
    • 109 |
    110 |
    111 |
  • 112 |
  • Maximize code reusability.

    113 |
    114 |
      115 |
    • Define functions from rows to rows (a one-to-one relationship), from rows to lists (a one-to-many relationship), from lists to lists, or lists to rows (aggregation).
    • 116 |
    • Such functions can include data from other tables.
    • 117 |
    118 |
    119 |
  • 120 |
  • Minimize boilerplate - setting up tables should be as easy as it is in SQLAlchemy.

    121 |
  • 122 |
123 |
124 |

Contributing

125 |

If you have a question then please raise an issue on Github here.

126 |
127 |
128 | 129 | 130 |
131 |
132 |
133 | 169 |
170 |
171 | 182 | 183 | 184 | 185 | 186 | 187 | -------------------------------------------------------------------------------- /docs/_build/html/motivation.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Motivation — LambdaQuery 0.1 documentation 10 | 11 | 12 | 13 | 14 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 |
42 |
43 |
44 |
45 | 46 |
47 |

Motivation

48 |

SQL is a pain in the ass to write.

49 |
50 |

Shortcomings of SQL

51 |
52 |
53 | 54 | 55 |
56 |
57 |
58 | 95 |
96 |
97 | 108 | 109 | 110 | 111 | 112 | 113 | -------------------------------------------------------------------------------- /docs/_build/html/objects.inv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xnmp/lambdaquery/4ede46cb8518ff393898ffaf12cebe530dc90627/docs/_build/html/objects.inv -------------------------------------------------------------------------------- /docs/_build/html/properties.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Defining Properties — LambdaQuery 0.1 documentation 10 | 11 | 12 | 13 | 14 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 |
42 |
43 |
44 |
45 | 46 |
47 |

Defining Properties

48 |

This is where LambdaQuery really shines.

49 |
50 |

Injective (One-to-One) Properties

51 |

jjj

52 |
53 |
54 |

One-to-Many Properties

55 |

Use Kleisli decorator

56 |
57 |
58 |
59 |

Composition

60 |

We can freely compose these functions.

61 |
62 | 63 | 64 |
65 |
66 |
67 | 106 |
107 |
108 | 119 | 120 | 121 | 122 | 123 | 124 | -------------------------------------------------------------------------------- /docs/_build/html/search.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Search — LambdaQuery 0.1 documentation 10 | 11 | 12 | 13 | 14 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 |
48 |
49 |
50 |
51 | 52 |

Search

53 |
54 | 55 |

56 | Please activate JavaScript to enable the search 57 | functionality. 58 |

59 |
60 |

61 | From here you can search these documents. Enter your search 62 | words into the box below and click "search". Note that the search 63 | function will automatically search for all of the words. Pages 64 | containing fewer words won't appear in the result list. 65 |

66 |
67 | 68 | 69 | 70 |
71 | 72 |
73 | 74 |
75 | 76 |
77 |
78 |
79 | 89 |
90 |
91 | 99 | 100 | 101 | 102 | 103 | 104 | -------------------------------------------------------------------------------- /docs/_build/html/searchindex.js: -------------------------------------------------------------------------------- 1 | Search.setIndex({docnames:["advanced","faq","index","motivation","properties","query","start"],envversion:51,filenames:["advanced.rst","faq.rst","index.rst","motivation.rst","properties.rst","query.rst","start.rst"],objects:{},objnames:{},objtypes:{},terms:{"break":5,"case":[2,5],"function":[0,2,4,5],"null":[2,5],"return":5,AND:5,And:5,Being:2,For:5,NOT:5,One:[2,5],Such:[2,5],THEN:5,That:5,The:[0,2,6],Then:5,Use:[4,5],Using:2,With:5,_tabl:6,abbrevi:5,abl:2,about:5,abov:5,access:5,actual:2,add:0,addit:5,advanc:2,again:5,aggreg:2,albatross:[],algorithm:0,all:[2,5],alreadi:[],alwai:5,amount:5,ani:5,annoi:5,anoth:5,answer:2,api:2,appear:5,appli:5,appropri:2,argument:5,art:5,ass:3,assign:2,attribut:5,automat:2,averag:5,avg:5,avg_:5,avg_count_no:5,avg_credit:5,bachelor:5,basic:0,becaus:5,becom:2,befor:[2,5],begin:5,benefit:0,better:5,bit:5,boilerpl:[2,5],bone:[],both:5,bracket:5,busi:5,bys:[0,2],campu:5,can:[0,2,4,5],chain:2,choos:2,clean:2,clone:6,close:5,coalesc:5,coalesce_:5,coalesce_count_cod:5,coalesce_count_no:5,code:[2,5],collaps:5,column:[0,2,5],com:[5,6],combin:[0,2],combinb:5,come:[1,5],commerc:5,commers:5,common:5,compil:[0,5],complet:6,complex:5,compos:[2,4,5],composit:2,concern:5,condit:5,construct:5,contain:5,content:2,convolut:5,cool:5,copi:5,count:5,count_:5,count_cod:5,count_no0:5,count_no1:5,count_no2:5,count_no:5,count_no_hw5k:[],count_no_upl4:5,cours:5,course_cod:5,course_fsu0:5,course_ie1k:5,course_l_hvog:[],course_l_m7nk:5,course_l_rwa0:5,course_l_tti0:5,coursestart:5,credit:5,crunchi:[],cs0:5,data:2,databas:[5,6],decor:[4,5],def:5,defin:[2,5],degre:5,demonstr:5,depart:5,depend:2,dept0:5,dept_18gw:5,dept_5xs8:[],dept_751:5,dept_cod:5,dept_ek40:5,dept_ilso:5,dept_j0k8:5,dept_l_m5yw:5,dept_y04o:5,describ:2,descript:5,diagram:5,dictionari:5,displai:5,distinct:5,doc:6,document:[],doe:2,don:5,done:[],down:5,each:5,easi:2,element:2,empti:5,enabl:[2,5],encapsul:2,end:5,english:2,enorm:5,entri:5,etc:5,even:5,everi:5,ex15:5,ex17:5,ex19:5,ex1:5,ex4:5,ex5:5,ex6:5,ex8:5,exampl:[5,6],exist:5,explanatori:5,explicit:2,fact:5,fairli:5,fals:0,faq:2,few:0,file:[],filter:2,first:5,fmap:5,folder:5,follow:[5,6],foreign:[2,5],found:5,four:5,freeli:4,frog:[],from:[2,5],futur:5,gannet:[],gener:[0,5],get:[0,5],getitem:5,git:6,github:[1,2,5,6],give:5,glanc:5,glimps:5,goal:2,going:5,graph:0,greater:5,group:2,had:5,handl:[2,5],hard:2,harder:5,has:5,have:[0,2,5],heavili:2,here:[1,2,5],herr:5,high:5,how:2,http:[5,6],illustr:5,incident:2,includ:2,index:[],inject:[2,5],innermost:5,insid:5,instal:2,instruct:6,intern:0,intuit:5,iron:5,issu:[1,2],iter:2,its:5,itself:5,jjj:4,join:2,just:[0,2,5],keep:5,kei:[2,5],keyword:5,kleisli:[2,4],know:[2,5],lambda:5,lambdaqueri:[0,2,4,5,6],languag:2,last:2,layer:2,least:5,left:2,let:5,librari:2,lies:5,lift:[2,5],like:[2,5],like_:5,like_no:5,list:[2,5],locat:5,logic:5,look:5,lost:2,mai:5,main:2,mani:[2,5],manipul:2,map:5,master:5,match:5,matter:5,max:5,max_:5,maxim:2,memori:2,method:[0,5],might:5,min:5,min_:5,minim:2,modul:[],monster:2,more:[0,2,5],most:5,motiv:2,much:2,must:5,name0:5,name:[0,2,5],navig:2,need:5,nois:2,north:5,note:5,now:5,num_dept:5,number:5,object:2,offer:5,one:[2,5],onli:5,open:5,oper:5,option:0,origin:5,orm:5,other:[2,5],our:5,out:5,outermost:5,outlin:6,over:5,page:[],pain:3,part:5,particular:[2,5],past:5,perform:0,phd:5,physic:5,pleas:[1,2],possibl:2,preced:5,preserv:2,pretti:5,primari:5,print:5,prog_5xf:[],prog_cod:5,prog_copy_iypc:5,prog_copy_q6g8:5,prog_iy3k:5,program:5,properti:[2,5],propeti:[],provisio:5,pure:2,python:[2,6],quantiti:[],queri:[0,2,6],query_gsd4:[],query_hw2g:[],query_upi0:5,question:2,rabbit:2,rais:[1,2],rampant:2,rang:5,raw:5,read:5,readabl:0,realli:[4,5],red:2,reduc:0,reduct:0,redund:5,refer:5,relationship:[2,5],remain:5,rememb:5,remov:2,replac:5,repo:5,represent:0,reroute_grz4:[],reroute_hvc0:[],reroute_hx8g:[],reroute_upew:5,reroute_uqjc:5,respect:5,result:[0,5],returnm:5,reusabl:2,rippl:[],row:[2,5],rule:2,run:[],sai:5,same:5,sc0:5,sc_7488:5,sc_bgo0:5,sc_im20:5,sc_j3io:5,sc_kda8:5,sc_pob4:5,sc_wuvk:[],sc_xzqo:5,school:5,school_cod:5,scienc:5,scope:2,search:[],second:5,section:6,see:[0,5],seen:5,select:2,self:5,sentenc:2,seri:5,set:[2,5],setup:6,sever:5,shine:4,shortcom:2,should:[2,5],show:5,signifi:5,similar:2,simpl:5,simpler:5,simpli:5,sinc:5,singl:5,solv:2,someth:2,soon:1,sourc:0,south:5,sql:[0,2,5],sqlalchemi:2,start:5,stick:[],strength:5,structur:[2,6],subqueri:2,sum:5,summar:5,suppos:5,syntact:2,syntax:[2,5],tabl:2,take:5,tape:2,tast:5,than:[2,5],thei:0,thi:[0,2,4,5,6],thing:5,think:5,thought:5,throawai:2,throwawai:2,time:5,titl:5,took:[],transform:0,treat:5,turn:5,two:[2,5],type:5,under:6,underscor:5,univers:5,unreduc:0,unrol:5,use:[0,5],used:5,using:5,usual:5,valu:5,veri:[2,5],version:5,via:5,wai:5,want:5,well:5,were:5,what:5,when:[2,5],where:[4,5],which:[2,5],whole:2,why:2,work:[2,5],worth:5,would:5,wouldn:[],wrap:0,write:[2,3,5],written:5,xnmp:[5,6],yield:5,you:[0,2,5]},titles:["Advanced","FAQ","Welcome to \u03bbQuery!","Motivation","Defining Properties","Querying","Installation"],titleterms:{"\u03bbqueri":2,One:4,The:5,Using:0,advanc:0,aggreg:5,befor:0,composit:4,contribut:2,defin:4,delight:[],depend:5,document:[],encapsul:5,explicit:5,faq:1,filter:5,frozen:[],group:[0,5],how:0,indic:[],inject:4,instal:6,iter:5,join:5,kleisli:0,lambdaqueri:[],last:0,left:5,lift:0,mani:4,motiv:3,object:5,properti:4,propeti:[],queri:5,select:5,set:6,shortcom:3,sql:3,tabl:[5,6],welcom:2,work:0}}) -------------------------------------------------------------------------------- /docs/_build/html/start.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Installation — LambdaQuery 0.1 documentation 10 | 11 | 12 | 13 | 14 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 |
42 |
43 |
44 |
45 | 46 |
47 |

Installation

48 |

Complete the following instructions:

49 |
    50 |
  • git clone https://github.com/xnmp/lambdaquery.git
  • 51 |
  • cd LambdaQuery
  • 52 |
  • python setup.py install.
  • 53 |
54 |
55 |
56 |

Setting up Tables

57 |

In Examples/example._tables.py there is an example of setting up tables. The structure of this database is outlined in these docs under the “Querying” section.

58 |
59 | 60 | 61 |
62 |
63 |
64 | 99 |
100 |
101 | 112 | 113 | 114 | 115 | 116 | 117 | -------------------------------------------------------------------------------- /docs/advanced.rst: -------------------------------------------------------------------------------- 1 | 2 | Advanced 3 | ========= 4 | 5 | 6 | Grouping By 7 | ----------- 8 | 9 | Grouping by 10 | 11 | 12 | Last Before 13 | ----------- 14 | 15 | 16 | 17 | 18 | Using Kleisli and Lifted 19 | ------------------------ 20 | 21 | If you wrap these you get a few benefits: 22 | 23 | * The resulting columns have more readable names 24 | * You can use the resulting functions as methods 25 | * They add on the group bys of their source. 26 | 27 | 28 | How it Works 29 | ============ 30 | 31 | Basically the internal representation of a query is as a graph. Query combinators are just transformations on this graph. Before generating the SQL, LambdaQuery performs a reduction algorithm on this graph, and then the reduced graph is compiled to SQL. To see the SQL of the unreduced graph, use the ``reduce=False`` option for the ``Query.sql`` method. -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # LambdaQuery documentation build configuration file, created by 5 | # sphinx-quickstart on Sat Oct 7 18:35:07 2017. 6 | # 7 | # This file is execfile()d with the current directory set to its 8 | # containing dir. 9 | # 10 | # Note that not all possible configuration values are present in this 11 | # autogenerated file. 12 | # 13 | # All configuration values have a default; values that are commented out 14 | # serve to show the default. 15 | 16 | # If extensions (or modules to document with autodoc) are in another directory, 17 | # add these directories to sys.path here. If the directory is relative to the 18 | # documentation root, use os.path.abspath to make it absolute, like shown here. 19 | # 20 | # import os 21 | # import sys 22 | # sys.path.insert(0, os.path.abspath('.')) 23 | 24 | 25 | # -- General configuration ------------------------------------------------ 26 | 27 | # If your documentation needs a minimal Sphinx version, state it here. 28 | # 29 | # needs_sphinx = '1.0' 30 | 31 | # Add any Sphinx extension module names here, as strings. They can be 32 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 33 | # ones. 34 | extensions = [] 35 | 36 | # Add any paths that contain templates here, relative to this directory. 37 | templates_path = ['_templates'] 38 | 39 | # The suffix(es) of source filenames. 40 | # You can specify multiple suffix as a list of string: 41 | # 42 | # source_suffix = ['.rst', '.md'] 43 | source_suffix = '.rst' 44 | 45 | # The master toctree document. 46 | master_doc = 'index' 47 | 48 | # General information about the project. 49 | project = 'LambdaQuery' 50 | copyright = '2017, Chong Wang' 51 | author = 'Chong Wang' 52 | 53 | # The version info for the project you're documenting, acts as replacement for 54 | # |version| and |release|, also used in various other places throughout the 55 | # built documents. 56 | # 57 | # The short X.Y version. 58 | version = '0.1' 59 | # The full version, including alpha/beta/rc tags. 60 | release = '0.1' 61 | 62 | # The language for content autogenerated by Sphinx. Refer to documentation 63 | # for a list of supported languages. 64 | # 65 | # This is also used if you do content translation via gettext catalogs. 66 | # Usually you set "language" from the command line for these cases. 67 | language = None 68 | 69 | # List of patterns, relative to source directory, that match files and 70 | # directories to ignore when looking for source files. 71 | # This patterns also effect to html_static_path and html_extra_path 72 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] 73 | 74 | # The name of the Pygments (syntax highlighting) style to use. 75 | pygments_style = 'sphinx' 76 | 77 | # If true, `todo` and `todoList` produce output, else they produce nothing. 78 | todo_include_todos = False 79 | 80 | 81 | # -- Options for HTML output ---------------------------------------------- 82 | 83 | # The theme to use for HTML and HTML Help pages. See the documentation for 84 | # a list of builtin themes. 85 | # 86 | # html_theme = 'scrolls' 87 | 88 | # Theme options are theme-specific and customize the look and feel of a theme 89 | # further. For a list of options available for each theme, see the 90 | # documentation. 91 | # 92 | # html_theme_options = {} 93 | 94 | # Add any paths that contain custom static files (such as style sheets) here, 95 | # relative to this directory. They are copied after the builtin static files, 96 | # so a file named "default.css" will overwrite the builtin "default.css". 97 | html_static_path = ['_static'] 98 | 99 | 100 | # -- Options for HTMLHelp output ------------------------------------------ 101 | 102 | # Output file base name for HTML help builder. 103 | htmlhelp_basename = 'LambdaQuerydoc' 104 | 105 | 106 | # -- Options for LaTeX output --------------------------------------------- 107 | 108 | latex_elements = { 109 | # The paper size ('letterpaper' or 'a4paper'). 110 | # 111 | # 'papersize': 'letterpaper', 112 | 113 | # The font size ('10pt', '11pt' or '12pt'). 114 | # 115 | # 'pointsize': '10pt', 116 | 117 | # Additional stuff for the LaTeX preamble. 118 | # 119 | # 'preamble': '', 120 | 121 | # Latex figure (float) alignment 122 | # 123 | # 'figure_align': 'htbp', 124 | } 125 | 126 | # Grouping the document tree into LaTeX files. List of tuples 127 | # (source start file, target name, title, 128 | # author, documentclass [howto, manual, or own class]). 129 | latex_documents = [ 130 | (master_doc, 'LambdaQuery.tex', 'LambdaQuery Documentation', 131 | 'Chong Wang', 'manual'), 132 | ] 133 | 134 | 135 | # -- Options for manual page output --------------------------------------- 136 | 137 | # One entry per manual page. List of tuples 138 | # (source start file, name, description, authors, manual section). 139 | man_pages = [ 140 | (master_doc, 'lambdaquery', 'LambdaQuery Documentation', 141 | [author], 1) 142 | ] 143 | 144 | 145 | # -- Options for Texinfo output ------------------------------------------- 146 | 147 | # Grouping the document tree into Texinfo files. List of tuples 148 | # (source start file, target name, title, author, 149 | # dir menu entry, description, category) 150 | texinfo_documents = [ 151 | (master_doc, 'LambdaQuery', 'LambdaQuery Documentation', 152 | author, 'LambdaQuery', 'One line description of project.', 153 | 'Miscellaneous'), 154 | ] 155 | 156 | 157 | 158 | -------------------------------------------------------------------------------- /docs/faq.rst: -------------------------------------------------------------------------------- 1 | FAQ 2 | ==== 3 | 4 | Coming soon! 5 | 6 | Please raise an issue on Github `here `_. -------------------------------------------------------------------------------- /docs/images/haskell_logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /docs/images/schema.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xnmp/lambdaquery/4ede46cb8518ff393898ffaf12cebe530dc90627/docs/images/schema.png -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. LambdaQuery documentation master file, created by 2 | sphinx-quickstart on Sat Oct 7 18:35:07 2017. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | 7 | Welcome to λQuery! 8 | ======================================= 9 | 10 | .. toctree:: 11 | :maxdepth: 2 12 | :caption: Contents 13 | 14 | motivation 15 | start 16 | query 17 | properties 18 | advanced 19 | faq 20 | 21 | This is a library that enables one to write composable SQL in pure python. It is heavily functional, enabling one to chain query combinators in incidentally very similar to the `Rabbit query language `_. 22 | 23 | The question that we solve - why is writing SQL so hard? How is it that something describable in two sentences in English becomes a monster of a query? How does one not become lost navigating the scoping rules and the throwaway names that become rampant when you have more than one layer of aggregation? LambdaQuery is the answer. 24 | 25 | The main goals of LambdaQuery are to be a query API that: 26 | 27 | * Removes as much syntactic noise as possible. 28 | 29 | * Joining by foreign keys automatically. 30 | * Handles all of the red tape in SQL. 31 | * Automatically assigns the throaway names to subqueries and columns of subqueries. 32 | * Knows which tables should be left joined and handles the null cases. 33 | * Knows which relationships are one to one, and choosing the group bys appropriately to preserve structure of the rows. 34 | 35 | * Syntax that is very similar to manipulating in-memory Python lists. 36 | 37 | * Being able to work with particular elements of a list or the list as a whole. 38 | * Having CLEAN syntax just like actual Python. 39 | 40 | * Maximize code reusability. 41 | 42 | * Define functions from rows to rows (a one-to-one relationship), from rows to lists (a one-to-many relationship), from lists to lists, or lists to rows (aggregation). 43 | * Such functions can include data from other tables. 44 | 45 | * Minimize boilerplate - setting up tables should be as easy as it is in SQLAlchemy. 46 | 47 | 48 | Contributing 49 | ------------ 50 | 51 | If you have a question then please raise an issue on Github `here `_. 52 | 53 | 54 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=. 11 | set BUILDDIR=_build 12 | set SPHINXPROJ=LambdaQuery 13 | 14 | if "%1" == "" goto help 15 | 16 | %SPHINXBUILD% >NUL 2>NUL 17 | if errorlevel 9009 ( 18 | echo. 19 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 20 | echo.installed, then set the SPHINXBUILD environment variable to point 21 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 22 | echo.may add the Sphinx directory to PATH. 23 | echo. 24 | echo.If you don't have Sphinx installed, grab it from 25 | echo.http://sphinx-doc.org/ 26 | exit /b 1 27 | ) 28 | 29 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 30 | goto end 31 | 32 | :help 33 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 34 | 35 | :end 36 | popd 37 | -------------------------------------------------------------------------------- /docs/motivation.rst: -------------------------------------------------------------------------------- 1 | Motivation 2 | ========== 3 | 4 | SQL is a pain in the ass to write. 5 | 6 | Shortcomings of SQL 7 | -------------------- 8 | 9 | 10 | -------------------------------------------------------------------------------- /docs/properties.rst: -------------------------------------------------------------------------------- 1 | 2 | Defining Properties 3 | =================== 4 | 5 | This is where LambdaQuery really shines. 6 | 7 | 8 | Injective (One-to-One) Properties 9 | --------------------------------- 10 | 11 | jjj 12 | 13 | 14 | One-to-Many Properties 15 | ---------------------- 16 | 17 | Use Kleisli decorator 18 | 19 | 20 | Composition 21 | =========== 22 | 23 | We can freely compose these functions. -------------------------------------------------------------------------------- /docs/query.rst: -------------------------------------------------------------------------------- 1 | 2 | Querying 3 | ======== 4 | 5 | For this we're going to be using the same examples as can be found `here `_. That is we have a database for a university, with four tables: 6 | 7 | * Schools - a university may have several schools, for example the school of science, the school of arts, and the school of business. The school table has the following columns: 8 | 9 | * A code (primary key) 10 | * A name 11 | * A campus - north campus, south campus, etc. 12 | 13 | * Programs - each school may offer several programs, or degrees - a bachelor of commerse, a bachelor of arts, or a master of arts. The program title has the following columns: 14 | 15 | * A code (primary key) 16 | * A school code (foreign key) 17 | * A title (e.g. bachelor of commerce) 18 | * A degree type - bachelor's, masters, phd, etc. 19 | 20 | * Departments - for example, the physics department might be a part of the school of science. Each department has: 21 | 22 | * A code (primary key) 23 | * A name 24 | * A school code (foreign key) 25 | 26 | * Courses - Departments offer courses, this is pretty self explanatory. each course has 27 | 28 | * A course code (primary key) 29 | * A department code (foreign key) 30 | * A number of credits 31 | * A description 32 | * A title 33 | 34 | This is summarized in the following diagram: 35 | 36 | .. image:: images/schema.png 37 | 38 | 39 | The Query Object 40 | ---------------- 41 | 42 | Tables are set up in the "Examples" folder of the github repo located at https://github.com/xnmp/lambdaquery. The boilerplate code should be fairly self-explanatory and it's worth glancing over it to see the syntax. 43 | 44 | Use the ``Query.sql`` method to display the SQL of a query. 45 | 46 | .. code-block:: python 47 | 48 | ex1 = School.query() 49 | print(ex1.sql()) 50 | 51 | This will give the result: 52 | 53 | .. code-block:: mysql 54 | 55 | SELECT DISTINCT 56 | sc_kda8.school_code AS code, 57 | sc_kda8.name AS name, 58 | sc_kda8.campus AS campus 59 | FROM school AS sc_kda8 60 | 61 | 62 | 63 | 64 | Filtering 65 | --------- 66 | 67 | Use the ``.filter`` method to filter: 68 | 69 | .. code-block:: python 70 | 71 | School.query().filter(lambda x: x.campus == 'south') 72 | 73 | Since this construction is so common, this can be abbreviated down to: 74 | 75 | .. code-block:: python 76 | 77 | School.query(lambda x: x.campus == 'south') 78 | 79 | The resulting SQL looks like: 80 | 81 | .. code-block:: mysql 82 | 83 | SELECT DISTINCT 84 | sc_pob4.school_code AS code, 85 | sc_pob4.name AS name, 86 | sc_pob4.campus AS campus 87 | FROM school AS sc_pob4 88 | WHERE (sc_pob4.campus = 'south') 89 | 90 | 91 | 92 | Selecting 93 | --------- 94 | 95 | To demonstrate selecting, we use the ``fmap`` method of the query object. Again, we can think of a query like a list, and the ``fmap`` method is like mapping a function over that list. Selection looks like: 96 | 97 | .. code-block:: python 98 | 99 | School.query().fmap(lambda x: x.name % x.campus) 100 | 101 | Here we can think of the query as a list of dictionaries where the entries of the dictionary can be accessed by using an attribute name. The ``%`` operator herre can be thought of the operator that combinbes two dictionaries. 102 | 103 | Note that because this is so common, a simpler way to do this is via the ``getitem`` method as follows: 104 | 105 | .. code-block:: python 106 | 107 | School.query()['name','campus'] 108 | 109 | This will give the result: 110 | 111 | .. code-block:: mysql 112 | 113 | SELECT DISTINCT 114 | sc_pob4.name AS name, 115 | sc_pob4.campus AS campus 116 | FROM school AS sc_pob4 117 | 118 | Now there's always several ways to do things in LambdaQuery, and its strength lies in using a generator function to "unroll" a query. We can do this via the ``yield`` keyword, so that when we write ``sc0 = yield School.query()`` this can be thought of as ``for sc0 in School.query():``. Then ``sc0`` is a single school, and we can apply functions that work on single schools to ``sc0``, such as, say, the number of departments it has. 119 | 120 | For reference, the above is written using the generator function as: 121 | 122 | .. code-block:: python 123 | 124 | @do(Query) 125 | def ex4(): 126 | sc0 = yield School.query() 127 | returnM (sc0.name, sc0.campus) 128 | 129 | which may be read as "for each school, return its name and campus". 130 | 131 | 132 | Joining 133 | ------- 134 | 135 | Joining is very simple when we only have one foreign key. For example, if we have a school ``sc0`` and we want its departments, then this is simply ``sc0.departments()`` which is itself a query object. Note the open-and-close bracket at the end - this is to signify a one-to-many relationship. For a one-to-one relationship such as the school of a department ``dept0``, we don't need the brackets and this is just ``dept0.school``. 136 | 137 | For example, suppose we wanted: 138 | 139 | for each department, return its name, the name of its school, and the campus of its school. 140 | 141 | This is written as: 142 | 143 | .. code-block:: python 144 | 145 | @do(Query) 146 | def ex6(): 147 | dept0 = yield Department.query() 148 | returnM ( 149 | dept0.name, 150 | dept0.school.name, 151 | dept0.school.campus 152 | ) 153 | 154 | And the SQL generated is: 155 | 156 | .. code-block:: mysql 157 | 158 | SELECT DISTINCT 159 | dept_ilso.name AS name, 160 | sc_im20.name AS name0, 161 | sc_im20.campus AS campus 162 | FROM department AS dept_ilso 163 | JOIN school AS sc_im20 ON (sc_im20.school_code = dept_ilso.school_code) 164 | 165 | 166 | 167 | 168 | Aggregation 169 | =========== 170 | 171 | Now the query object has the ``min``, ``max``, ``avg``, ``sum``, and ``count`` methods, which collapse a query down into one number. So suppose for each school, we want the number of departments. This is written as: 172 | 173 | .. code-block:: python 174 | 175 | @do(Query) 176 | def ex5(): 177 | sc0 = yield School.query() 178 | returnM ( 179 | sc0.name, 180 | sc0.departments().count() 181 | ) 182 | 183 | The generated SQL is: 184 | 185 | .. code-block:: mysql 186 | 187 | SELECT 188 | sc_xzqo.name AS name, 189 | COUNT(DISTINCT dept_y04o.dept_code) AS count_code 190 | FROM school AS sc_xzqo 191 | JOIN department AS dept_y04o ON (dept_y04o.school_code = sc_xzqo.school_code) 192 | GROUP BY 1 193 | 194 | 195 | Encapsulation 196 | --------------- 197 | 198 | Here we get our first glimpse into the enormous amount of composability offered by LambdaQuery. We can write the above a function as follows:: 199 | 200 | def num_dept(self): 201 | return self.departments().count() 202 | 203 | And now the above can be written as:: 204 | 205 | @do(Query) 206 | def ex5(): 207 | sc0 = yield School.query() 208 | returnM ( 209 | sc0.name, 210 | num_dept(sc0) 211 | ) 212 | 213 | If we want to use it as if it were an attribute, then we need to use the ``@injective`` decorator:: 214 | 215 | @injective() 216 | def num_dept(self): 217 | return self.departments().count() 218 | 219 | This enables us to write:: 220 | 221 | @do(Query) 222 | def ex5(): 223 | sc0 = yield School.query() 224 | returnM ( 225 | sc0.name, 226 | sc0.num_dept 227 | ) 228 | 229 | In fact, as before we can now write ``School.query()['name','num_dept']`` to get the same result. 230 | 231 | We can even filter on this: ``School.query(lambda x: x.num_dept > 3)`` returns the SQL: 232 | 233 | .. code-block:: mysql 234 | 235 | SELECT 236 | sc_7488.school_code AS code, 237 | sc_7488.name AS name, 238 | sc_7488.campus AS campus 239 | FROM school AS sc_7488 240 | JOIN department AS dept_751s ON (dept_751s.school_code = sc_7488.school_code) 241 | GROUP BY 1, 2, 3 242 | HAVING (COUNT(DISTINCT dept_751s.dept_code) > 3) 243 | 244 | In every respect the ``num_dept`` property that we have just defined may be treated as if it were just another column. 245 | 246 | 247 | Explicit Grouping By 248 | ----------------------- 249 | 250 | Grouping by is a matter of using the functions ``max_``, ``min_``, ``count_`` (with an underscore). Note that these functions take a single row to a single row, and are the only cases of breaking the intuition of thinking of queries as lists. Such functions should only be used inside the ``returnM``. 251 | 252 | Suppose we wanted to get the average number of credits of courses with a code that begins with "100". This looks like:: 253 | 254 | @do(Query) 255 | def ex8(): 256 | cs0 = yield Course.query() 257 | returnM ( 258 | cs0.no.like_('100%'), 259 | cs0.credits.avg_() 260 | ) 261 | 262 | Generated SQL: 263 | 264 | .. code-block:: mysql 265 | 266 | SELECT 267 | course_ie1k.course_code LIKE '100%' AS like_no, 268 | AVG(COALESCE(course_ie1k.credits, 0)) AS avg_credits 269 | FROM course AS course_ie1k 270 | GROUP BY 1 271 | 272 | 273 | Iteration 274 | ========= 275 | 276 | Suppose for each department, we wanted to know the number of 100 series courses it offered, and the same for 200 series, 300 series, and 400 series. In SQL, this looks like: 277 | 278 | .. code-block:: mysql 279 | 280 | SELECT 281 | dept_ek40.name AS name, 282 | COUNT(DISTINCT CASE WHEN (course_fsu0.course_code > 100) AND (course_fsu0.course_code < 199) THEN course_fsu0.course_code END) AS count_no, 283 | COUNT(DISTINCT CASE WHEN (course_fsu0.course_code > 200) AND (course_fsu0.course_code < 299) THEN course_fsu0.course_code END) AS count_no0, 284 | COUNT(DISTINCT CASE WHEN (course_fsu0.course_code > 300) AND (course_fsu0.course_code < 399) THEN course_fsu0.course_code END) AS count_no1, 285 | COUNT(DISTINCT CASE WHEN (course_fsu0.course_code > 400) AND (course_fsu0.course_code < 499) THEN course_fsu0.course_code END) AS count_no2 286 | FROM department AS dept_ek40 287 | JOIN course AS course_fsu0 ON (course_fsu0.dept_code = dept_ek40.dept_code) 288 | GROUP BY 1 289 | 290 | To write this out, one would usually copy and paste the column and replace the number every time. We can do better. To illustrate, let's first define a property for if the code is of a particular series number:: 291 | 292 | @Lifted 293 | def coursestart(course, start): 294 | return (course.no > start) & (course.no < start + 99) 295 | 296 | Note that this function can take a second argument: ``start``. Now let's use it like so:: 297 | 298 | @do(Query) 299 | def ex17(): 300 | dept0 = yield Department.query() 301 | returnM ( 302 | dept0.name, 303 | *[dept0.courses(lambda x: x.coursestart(i*100)).count() 304 | for i in range(1,5)] 305 | ) 306 | 307 | This is course what generated the original SQL. 308 | 309 | 310 | Left Joining 311 | ============= 312 | 313 | This is a matter of using the ``lj`` method. Intuitively, this turns an empty list into a list containing a single null value, and keeps every other list the same. 314 | 315 | For example, suppose we want 316 | 317 | for each department, its name, and the number of courses with greater than 2 credits. 318 | 319 | Note that it may be that a department offers no courses with greater than 2 credits, so this is a case where we need to left join. Namely:: 320 | 321 | @do(Query) 322 | def ex15(): 323 | dept0 = yield Department.query() 324 | returnM ( 325 | dept0.name, 326 | dept0.courses(lambda x: x.credits > 2).lj().count().coalesce_(0) 327 | ) 328 | 329 | Generated SQL: 330 | 331 | .. code-block:: mysql 332 | 333 | SELECT 334 | dept_18gw.name AS name, 335 | COALESCE(COUNT(DISTINCT course_l_rwa0.course_code), 0) AS coalesce_count_no 336 | FROM department AS dept_18gw 337 | LEFT JOIN course AS course_l_rwa0 ON (course_l_rwa0.credits > 2) 338 | AND (course_l_rwa0.dept_code = dept_18gw.dept_code) 339 | GROUP BY 1 340 | 341 | 342 | Left Joining Dependent Tables 343 | ------------------------------ 344 | 345 | One annoying thing about SQL can be seen in the following example: suppose we wanted, for each school, the number of departments that had at least one course that offered a course with at least 2 credits. 346 | 347 | We have to remember that the course table must be left joined as well, and any reference to the department table must come with the additional provisio that the course table is not null. 348 | 349 | LambdaQuery:: 350 | 351 | @do(Query) 352 | def ex15(): 353 | sc0 = yield School.query() 354 | returnM ( 355 | sc0.name, 356 | sc0.departments(lambda x: x.courses(lambda y: y.credits > 2) 357 | .exists()) 358 | .lj().count().coalesce_(0) 359 | ) 360 | 361 | The generated SQL shows that all of the preceding concerns are handled for us: 362 | 363 | .. code-block:: mysql 364 | 365 | SELECT 366 | sc_bgo0.name AS name, 367 | COALESCE(COUNT(DISTINCT CASE WHEN course_l_m7nk.course_code IS NOT NULL THEN dept_l_m5yw.dept_code END), 0) AS coalesce_count_code 368 | FROM school AS sc_bgo0 369 | LEFT JOIN department AS dept_l_m5yw ON (dept_l_m5yw.school_code = sc_bgo0.school_code) 370 | LEFT JOIN course AS course_l_m7nk ON course_l_m7nk.course_code IS NOT NULL 371 | AND (course_l_m7nk.dept_code = dept_l_m5yw.dept_code) 372 | AND (course_l_m7nk.credits > 2) 373 | GROUP BY 1 374 | 375 | Pretty cool eh? This really is just the beginning. Most ORMs start getting really convoluted when you write more complex queries, and it's usually harder than just writing raw SQL. With LambdaQuery it remains simple and intuitive, and the SQL compiler works out all the bits of SQL logic so you don't have to. 376 | 377 | We give just one more example here as a taste, and this is about as complex as it gets using the tables that we have. Suppose we want, for each school with a course that has at least 5 programs with a title that matches the school's name, the average number of high credit courses offered by its departments. Here a high credit course is one with greater than 3 departments. 378 | 379 | Here we go:: 380 | 381 | @do(Query) 382 | def ex19(): 383 | sc0 = yield School.query(lambda x: x.programs(lambda y: y.title == x.name) 384 | .count() >= 5) 385 | returnM ( 386 | sc0.name, 387 | sc0.departments() 388 | .fmap(lambda x: x.courses(lambda y: y.credits > 3).count()) 389 | .lj().avg() 390 | ) 391 | 392 | 393 | Generated SQL: 394 | 395 | .. code-block:: mysql 396 | 397 | SELECT 398 | query_upi0.reroute_uqjc AS name, 399 | AVG(COALESCE(query_upi0.count_no_upl4, 0)) AS avg_count_no 400 | FROM ( 401 | SELECT 402 | sc_j3io.school_code AS reroute_upew, 403 | sc_j3io.name AS reroute_uqjc, 404 | COUNT(DISTINCT course_l_tti0.course_code) AS count_no_upl4 405 | FROM school AS sc_j3io 406 | JOIN department AS dept_j0k8 ON (dept_j0k8.school_code = sc_j3io.school_code) 407 | JOIN program AS prog_iy3k ON (prog_iy3k.school_code = sc_j3io.school_code) 408 | AND (prog_iy3k.title = sc_j3io.name) 409 | LEFT JOIN course AS course_l_tti0 ON (course_l_tti0.dept_code = dept_j0k8.dept_code) 410 | AND (course_l_tti0.credits > 3) 411 | GROUP BY 1, 2, dept_j0k8.dept_code 412 | HAVING (COUNT(DISTINCT prog_iy3k.prog_code) >= 5) 413 | ) AS query_upi0 414 | JOIN program AS prog_copy_iypc ON (prog_copy_iypc.school_code = query_upi0.reroute_upew) 415 | AND (prog_copy_iypc.title = query_upi0.reroute_uqjc) 416 | GROUP BY 1 417 | HAVING (COUNT(DISTINCT prog_copy_iypc.prog_code) >= 5) 418 | 419 | One of the things to iron out in future versions is the redundant condition ``HAVING (COUNT(DISTINCT prog_copy_q6g8.prog_code) >= 5)`` appearing in both the innermost query and the outermost query. 420 | -------------------------------------------------------------------------------- /docs/start.rst: -------------------------------------------------------------------------------- 1 | 2 | Installation 3 | ======================================= 4 | 5 | Complete the following instructions: 6 | 7 | * ``git clone https://github.com/xnmp/lambdaquery.git`` 8 | * ``cd LambdaQuery`` 9 | * ``python setup.py install``. 10 | 11 | 12 | Setting up Tables 13 | ================== 14 | 15 | In ``Examples/example._tables.py`` there is an example of setting up tables. The structure of this database is outlined in these docs under the "Querying" section. -------------------------------------------------------------------------------- /example_tables.py: -------------------------------------------------------------------------------- 1 | from LambdaQuery import * 2 | from datetime import timedelta 3 | 4 | 5 | class School(Columns): 6 | table = Table("school", 'sc') 7 | def __init__(self): 8 | School.makeTable() 9 | self.baseExpr("school_code", 'code', primary=True) 10 | self.baseExpr("name", 'name') 11 | self.baseExpr("campus", "campus") 12 | 13 | class Department(Columns): 14 | table = Table("department", "dept") 15 | def __init__(self): 16 | Department.makeTable() 17 | self.baseExpr("dept_code", 'code', primary=True) 18 | self.baseExpr("school_code", 'sc_code', foreign=School) 19 | self.baseExpr("name", "name") 20 | 21 | class Program(Columns): 22 | table = Table("program", 'prog') 23 | def __init__(self): 24 | Program.makeTable() 25 | self.baseExpr("prog_code", 'code', primary=True) 26 | self.baseExpr("school_code", 'sc_code', foreign=School) 27 | self.baseExpr("title", "title") 28 | self.baseExpr("degree", 'degree') 29 | 30 | class Course(Columns): 31 | table = Table("course", "course") 32 | def __init__(self): 33 | Course.makeTable() 34 | self.baseExpr("course_code", 'no', primary=True) 35 | self.baseExpr("dept_code", 'dept_code', foreign=Department) 36 | self.baseExpr("description", "description") 37 | self.baseExpr("title", "title") 38 | self.baseExpr("credits", "credits") 39 | 40 | 41 | # ============================================================================ 42 | # instantiate all classes so that the primary and foreign keys are all defined 43 | # ============================================================================ 44 | 45 | for colclass in [subcls for subcls in Columns.__subclasses__()] \ 46 | + [subsubcls for subcls in Columns.__subclasses__() for subsubcls in subcls.__subclasses__()]: 47 | colclass() 48 | -------------------------------------------------------------------------------- /examples.py: -------------------------------------------------------------------------------- 1 | 2 | from example_tables import * 3 | 4 | # %% ^━━━━━━━━━━━━━━━━━━ ━━━━━━━━━━━━━━━━━━━━━━━━━━━^ 5 | 6 | ex1 = School.query() 7 | ex2 = School.query(lambda x: x.campus == 'south') 8 | ex3 = School.query().sort(lambda x: x.campus) 9 | ex4 = School.query().fmap(lambda x: x.name % x.campus) 10 | ex4 = School.query()['name','campus'] 11 | 12 | @do(Query) 13 | def ex4(): 14 | sc0 = yield School.query() 15 | returnM (sc0.name, sc0.campus) 16 | 17 | @do(Query) 18 | def ex5(): 19 | sc0 = yield School.query() 20 | returnM ( 21 | sc0.name, 22 | sc0.departments().count() 23 | ) 24 | 25 | @do(Query) 26 | def ex6(): 27 | dept0 = yield Department.query() 28 | returnM ( 29 | dept0.name, 30 | dept0.school.name, 31 | dept0.school.campus 32 | ) 33 | 34 | @injective() 35 | def num_dept(self): 36 | return self.departments().count() 37 | 38 | @do(Query) 39 | def ex7(): 40 | sc0 = yield School.query(lambda x: x.num_dept > 3) 41 | returnM ( 42 | sc0.code, 43 | sc0.num_dept 44 | ) 45 | 46 | ex7 = School.query(lambda x: x.num_dept > 3).fmap(lambda x: x.code % x.num_dept) 47 | ex7 = School.query(lambda x: x.num_dept > 3)[['code','num_dept']] 48 | 49 | @do(Query) 50 | def ex8(): 51 | cs0 = yield Course.query() 52 | returnM ( 53 | cs0.no.like_('100%'), 54 | cs0.credits.avg_() 55 | ) 56 | 57 | @do(Query) 58 | def ex9(): 59 | sc0 = yield School.query() 60 | dept0 = yield sc0.departments() 61 | cs0 = yield dept0.courses() 62 | returnM ( 63 | sc0.name, 64 | dept0.name, 65 | cs0.title 66 | ) 67 | 68 | # %% ^━━━━━━━━━━━━━━━━━━ ━━━━━━━━━━━━━━━━━━━━━━━━━━━^ 69 | 70 | # AGGREGATION 71 | 72 | ex10 = Department.query().count() 73 | 74 | ex11 = School.query().avg('num_dept') 75 | 76 | @do(Query) 77 | def ex12(): 78 | dept0 = yield Department.query() 79 | cs = dept0.courses() 80 | returnM ( 81 | dept0.name, 82 | cs.count(), 83 | cs.max('credits'), 84 | cs.sum('credits'), 85 | dept0.courses(lambda x: x.title.like_('Introduction%')).count() 86 | ) 87 | 88 | # %% ^━━━━━━━━━━━━━━━━━━ ━━━━━━━━━━━━━━━━━━━━━━━━━━━^ 89 | 90 | 91 | @do(Query) 92 | def ex13(): 93 | sc0 = yield School.query() 94 | returnM ( 95 | sc0.name, 96 | sc0.programs()['degree'].count(), 97 | ) 98 | 99 | 100 | @do(Query) 101 | def ex14(): 102 | dept0 = yield Department.query() 103 | returnM ( 104 | dept0.name, 105 | dept0.school.campus 106 | ) 107 | 108 | 109 | @do(Query) 110 | def ex15(): 111 | dept0 = yield Department.query() 112 | returnM ( 113 | dept0.name, 114 | dept0.courses(lambda x: x.credits > 2).lj().count().coalesce_(0) 115 | ) 116 | 117 | @do(Query) 118 | def ex15(): 119 | sc0 = yield School.query() 120 | returnM ( 121 | sc0.name, 122 | sc0.departments(lambda x: x.courses(lambda y: y.credits > 2) 123 | .exists()) 124 | .lj().count().coalesce_(0) 125 | ) 126 | 127 | @do(Query) 128 | def ex16(): 129 | sc0 = yield School.query() 130 | returnM ( 131 | sc0.campus, 132 | sc0.departments().lj().count() 133 | ) 134 | 135 | @Lifted 136 | def coursestart(course, start): 137 | return (course.no > start) & (course.no < start + 99) 138 | 139 | 140 | @do(Query) 141 | def ex17(): 142 | dept0 = yield Department.query() 143 | returnM ( 144 | dept0.name, 145 | *[dept0.courses(lambda x: x.coursestart(i*100)).count() for i in range(1,5)] 146 | ) 147 | 148 | @injective() 149 | def high_credit_count(self): 150 | return self.courses(lambda x: x.credits > 3).count() 151 | 152 | @do(Query) 153 | def ex18(): 154 | sc0 = yield School.query(lambda x: x.programs().exists()) 155 | returnM ( 156 | sc0.name, 157 | sc0.departments()['high_credit_count'].avg() 158 | ) 159 | 160 | 161 | @do(Query) 162 | def ex19(): 163 | sc0 = yield School.query(lambda x: x.programs(lambda y: y.title == x.name) 164 | .count() >= 5) 165 | returnM ( 166 | sc0.name, 167 | sc0.departments() 168 | .fmap(lambda x: x.courses(lambda y: y.credits > 3).count()) 169 | .lj().avg() 170 | ) 171 | 172 | print(ex19().sql(debug=True)) 173 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | description-file = README.md 3 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | setup( 4 | name='lambdaquery', 5 | version='0.1.21', 6 | description='Composable SQL in Pure Python', 7 | author='Chong Wang', 8 | author_email='chonw89@gmail.com', 9 | packages=['LambdaQuery'], 10 | license='MIT', 11 | # url=['https://github.com/xnmp/lambdaquery'], 12 | setup_requires=['lenses'], 13 | keywords='databases query sql orm', 14 | install_requires=['lenses'], 15 | python_requires='>=3.6' 16 | ) 17 | 18 | # cd LambdaQuery/ 19 | # python setup.py sdist upload 20 | # cd .. 21 | # pip install lambdaquery --upgrade --------------------------------------------------------------------------------