Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
0da66b0
Absolute imports and circular import fixes
Jan 31, 2018
c9ff3bd
Removing iterkeys
Jan 31, 2018
3f4c436
Removing filters
Jan 31, 2018
94e36c6
Removing map()
Jan 31, 2018
8d56839
Python 3 string handling
Jan 31, 2018
6fb03ab
iteritems and itervalues replaced with items and values
Jan 31, 2018
6020010
Fixing .values
Jan 31, 2018
0130c9e
Python 3 zip support
Jan 31, 2018
c98561c
six for xrange
Jan 31, 2018
9e96b65
Fixing uses of next
Jan 31, 2018
0e1d8bf
Merge branch 'comprehensions' into allpy3
Jan 31, 2018
b86aad8
Using six instead
Jan 31, 2018
2c4038d
Correct types
Jan 31, 2018
e65d6c8
Merge branch 'str-six' into allpy3
Jan 31, 2018
b0c7566
Merge branch 'zip' into allpy3
Jan 31, 2018
e93cee9
Merge branch 'iterators' into allpy3
Jan 31, 2018
d622725
Merge branch 'xrange' into allpy3
Jan 31, 2018
1f2a71d
Fixing python 3 issues
Jan 31, 2018
8e63f43
Merge branch 'iterators' into allpy3
Jan 31, 2018
8a1793b
functools.reduce instead of reduce
Jan 31, 2018
6b61da9
Merge branch 'iterators' into allpy3
Jan 31, 2018
0a66a03
Merge branch 'mods' into allpy3-mod
Jan 31, 2018
8ed1a52
Adding support for LAST
Feb 13, 2018
88c945a
Merge branch 'last-function' into allpy3-mod
Feb 13, 2018
711d4fb
Merge branch 'group-concat' into allpy3-mod
Feb 13, 2018
45e8902
Fix num_rows when loading table from JSON
millar Feb 14, 2018
0726480
Merge branch 'join-ordering' into allpy3-mod
Feb 14, 2018
5235452
Merge branch 'join-ordering' into allpy3-mod
Feb 14, 2018
34572a6
Merge pull request #12 from mixcloud/fix-num-rows
millar Feb 14, 2018
2cc6d80
Merge branch 'group-concat' into allpy3-mod
Feb 15, 2018
0dafe16
Merge branch 'allpy3-mod' of github.com:mixcloud/tinyquery into allpy…
Feb 15, 2018
514979e
Add string_agg
millar Mar 14, 2018
6fe4b49
Fix datetime typo (#14)
millar Apr 18, 2018
12cc001
Add replace function
millar May 29, 2018
a67289b
Add to replace to dict
millar May 29, 2018
108d8de
Merge pull request #13 from mixcloud/sm-rename-group-concat
millar May 29, 2018
0bf94f4
Merge branch 'allpy3-mod' into sm-replace-p3
millar May 29, 2018
b58a3d9
Fix
millar May 29, 2018
4951b85
Add FORMAT_TIMESTAMP
millar Mar 14, 2019
64a848f
Fix test
millar Mar 14, 2019
a39fd31
Add tests
millar Mar 14, 2019
74ad3a1
Merge branch 'sm-replace-p3' into all-patches
millar Mar 14, 2019
3224623
Fix arg order
millar May 15, 2019
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
url='https://github.com/Khan/tinyquery',
keywords=['bigquery'],
packages=['tinyquery'],
install_requires=['arrow==0.12.1', 'ply==3.10'],
install_requires=['arrow==0.12.1', 'ply==3.10', 'six==1.11.0'],
classifiers=[
'License :: OSI Approved :: MIT License',
'Programming Language :: Python :: 2',
Expand Down
14 changes: 9 additions & 5 deletions tinyquery/api_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,13 @@

This can be used in place of the value returned by apiclient.discovery.build().
"""
from __future__ import absolute_import

import functools
import json

import six


class TinyQueryApiClient(object):
def __init__(self, tq_service):
Expand Down Expand Up @@ -36,7 +40,7 @@ def __init__(self, func, args, kwargs):
self.args = args
self.kwargs = kwargs

def execute(self):
def execute(self, **kwargs):
return self.func(*self.args, **self.kwargs)


Expand Down Expand Up @@ -153,7 +157,7 @@ def insert(self, projectId, body):
create_disposition, write_disposition)
else:
assert False, 'Unknown job type: {}'.format(
body['configuration'].keys())
list(body['configuration'].keys()))

@staticmethod
def _get_config_table(config, key):
Expand Down Expand Up @@ -225,16 +229,16 @@ def schema_from_table(table):
"""Given a tinyquery.Table, build an API-compatible schema."""
return {'fields': [
{'name': name, 'type': col.type}
for name, col in table.columns.iteritems()
for name, col in table.columns.items()
]}


def rows_from_table(table):
"""Given a tinyquery.Table, build an API-compatible rows object."""
result_rows = []
for i in xrange(table.num_rows):
for i in six.moves.xrange(table.num_rows):
field_values = [{'v': str(col.values[i])}
for col in table.columns.itervalues()]
for col in table.columns.values()]
result_rows.append({
'f': field_values
})
Expand Down
10 changes: 6 additions & 4 deletions tinyquery/api_client_test.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
from __future__ import absolute_import

import unittest

import api_client
import tq_types
import tinyquery
from tinyquery import api_client
from tinyquery import tq_types
from tinyquery import tinyquery


class ApiClientTest(unittest.TestCase):
Expand Down Expand Up @@ -144,7 +146,7 @@ def test_table_copy(self):
}
).execute()

for _ in xrange(5):
for _ in range(5):
self.tq_service.jobs().insert(
projectId='test_project',
body={
Expand Down
60 changes: 31 additions & 29 deletions tinyquery/compiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,20 @@
-Validate that the expression is well-typed.
-Resolve all select fields to their aliases and types.
"""
from __future__ import absolute_import

import collections
import itertools

import parser
import runtime
import tq_ast
import typed_ast
import type_context
import tq_types

import six

class CompileError(Exception):
pass
from tinyquery import exceptions
from tinyquery import parser
from tinyquery import runtime
from tinyquery import tq_ast
from tinyquery import typed_ast
from tinyquery import type_context
from tinyquery import tq_types


def compile_text(text, tables_by_name):
Expand Down Expand Up @@ -93,7 +94,7 @@ def expand_select_fields(self, select_fields, table_expr):
"""
table_ctx = table_expr.type_ctx
star_select_fields = []
for table_name, col_name in table_ctx.columns.iterkeys():
for table_name, col_name in table_ctx.columns:
if table_name is not None:
col_ref = table_name + '.' + col_name
else:
Expand All @@ -112,9 +113,9 @@ def expand_select_fields(self, select_fields, table_expr):
elif (field.expr and isinstance(field.expr, tq_ast.ColumnId) and
field.expr.name.endswith('.*')):
prefix = field.expr.name[:-len('.*')]
record_star_fields = filter(
lambda f: f.alias.startswith(prefix),
star_select_fields)
record_star_fields = [f
for f in star_select_fields
if f.alias.startswith(prefix)]
result_fields.extend(record_star_fields)
else:
result_fields.append(field)
Expand Down Expand Up @@ -213,7 +214,7 @@ def compile_table_expr(self, table_expr):
return method(table_expr)

def compile_table_expr_TableId(self, table_expr):
import tinyquery # TODO(colin): fix circular import
from tinyquery import tinyquery # TODO(colin): fix circular import
table = self.tables_by_name[table_expr.name]
if isinstance(table, tinyquery.Table):
return self.compile_table_ref(table_expr, table)
Expand All @@ -225,7 +226,7 @@ def compile_table_expr_TableId(self, table_expr):
def compile_table_ref(self, table_expr, table):
alias = table_expr.alias or table_expr.name
columns = collections.OrderedDict([
(name, column.type) for name, column in table.columns.iteritems()
(name, column.type) for name, column in table.columns.items()
])
type_ctx = type_context.TypeContext.from_table_and_columns(
alias, columns, None)
Expand Down Expand Up @@ -265,8 +266,7 @@ def compile_table_expr_Join(self, table_expr):
[table_expr.base],
(join_part.table_expr for join_part in table_expr.join_parts)
)
compiled_result = map(self.compile_joined_table,
table_expressions)
compiled_result = [self.compile_joined_table(x) for x in table_expressions]
compiled_table_exprs, compiled_aliases = zip(*compiled_result)
type_contexts = [compiled_table.type_ctx
for compiled_table in compiled_table_exprs]
Expand All @@ -280,9 +280,11 @@ def compile_table_expr_Join(self, table_expr):
type_contexts)
return typed_ast.Join(
base=compiled_table_exprs[0],
tables=zip(compiled_table_exprs[1:],
(join_part.join_type
for join_part in table_expr.join_parts)),
# wrapping in list() for python 3 support (shouldn't be a large number
# of items so performance impact should be minimal)
tables=list(zip(compiled_table_exprs[1:],
(join_part.join_type
for join_part in table_expr.join_parts))),
conditions=result_fields,
type_ctx=result_type_ctx)

Expand All @@ -294,7 +296,7 @@ def compile_joined_table(self, table_expr):
elif isinstance(table_expr, tq_ast.TableId):
alias = table_expr.name
else:
raise CompileError('Table expression must have an alias name.')
raise exceptions.CompileError('Table expression must have an alias name.')
result_ctx = compiled_table.type_ctx.context_with_full_alias(alias)
compiled_table = compiled_table.with_type_ctx(result_ctx)
return compiled_table, alias
Expand Down Expand Up @@ -366,7 +368,7 @@ def compile_join_field(expr, join_type):
left_column_id)]
# Fall through to the error case if the aliases are the
# same for both sides.
raise CompileError('JOIN conditions must consist of an AND of = '
raise exceptions.CompileError('JOIN conditions must consist of an AND of = '
'comparisons between two field on distinct '
'tables. Got expression %s' % expr)
return [compile_join_field(expr, join_type)
Expand Down Expand Up @@ -434,7 +436,7 @@ def compile_groups(self, groups, select_fields, aliases, table_ctx):
def compile_select_field(self, expr, alias, within_clause, type_ctx):
if within_clause is not None and within_clause != 'RECORD' and (
expr.args[0].name.split('.')[0] != within_clause):
raise CompileError('WITHIN clause syntax error')
raise exceptions.CompileError('WITHIN clause syntax error')
else:
compiled_expr = self.compile_expr(expr, type_ctx)
return typed_ast.SelectField(compiled_expr, alias, within_clause)
Expand Down Expand Up @@ -467,7 +469,7 @@ def compile_Literal(self, expr, type_ctx):
return typed_ast.Literal(expr.value, tq_types.INT)
if isinstance(expr.value, float):
return typed_ast.Literal(expr.value, tq_types.FLOAT)
elif isinstance(expr.value, basestring):
elif isinstance(expr.value, six.string_types):
return typed_ast.Literal(expr.value, tq_types.STRING)
elif expr.value is None:
return typed_ast.Literal(expr.value, tq_types.NONETYPE)
Expand All @@ -485,7 +487,7 @@ def compile_UnaryOperator(self, expr, type_ctx):
try:
result_type = func.check_types(compiled_val.type)
except TypeError:
raise CompileError('Invalid type for operator {}: {}'.format(
raise exceptions.CompileError('Invalid type for operator {}: {}'.format(
expr.operator, [compiled_val.type]))
return typed_ast.FunctionCall(func, [compiled_val], result_type)

Expand All @@ -501,7 +503,7 @@ def compile_BinaryOperator(self, expr, type_ctx):
result_type = func.check_types(compiled_left.type,
compiled_right.type)
except TypeError:
raise CompileError('Invalid types for operator {}: {}'.format(
raise exceptions.CompileError('Invalid types for operator {}: {}'.format(
expr.operator, [arg.type for arg in [compiled_left,
compiled_right]]))

Expand All @@ -516,7 +518,7 @@ def compile_FunctionCall(self, expr, type_ctx):
# that the evaluator knows to change the context.
if self.is_innermost_aggregate(expr):
if type_ctx.aggregate_context is None:
raise CompileError('Unexpected aggregate function.')
raise exceptions.CompileError('Unexpected aggregate function.')
sub_expr_ctx = type_ctx.aggregate_context
ast_type = typed_ast.AggregateFunctionCall
else:
Expand All @@ -530,7 +532,7 @@ def compile_FunctionCall(self, expr, type_ctx):
result_type = func.check_types(
*(arg.type for arg in compiled_args))
except TypeError:
raise CompileError('Invalid types for function {}: {}'.format(
raise exceptions.CompileError('Invalid types for function {}: {}'.format(
expr.name, [arg.type for arg in compiled_args]))
return ast_type(func, compiled_args, result_type)

Expand All @@ -557,7 +559,7 @@ def get_aliases(cls, select_field_list):
for alias in proposed_aliases:
if alias is not None:
if alias in used_aliases:
raise CompileError(
raise exceptions.CompileError(
'Ambiguous column name {}.'.format(alias))
used_aliases.add(alias)

Expand Down
33 changes: 18 additions & 15 deletions tinyquery/compiler_test.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
# TODO(colin): fix these lint errors (http://pep8.readthedocs.io/en/release-1.7.x/intro.html#error-codes)
# pep8-disable:E122
from __future__ import absolute_import

import collections
import datetime
import unittest

import compiler
import context
import runtime
import tinyquery
import tq_ast
import tq_modes
import tq_types
import type_context
import typed_ast
from tinyquery import exceptions
from tinyquery import compiler
from tinyquery import context
from tinyquery import runtime
from tinyquery import tinyquery
from tinyquery import tq_ast
from tinyquery import tq_modes
from tinyquery import tq_types
from tinyquery import type_context
from tinyquery import typed_ast


class CompilerTest(unittest.TestCase):
Expand Down Expand Up @@ -124,7 +127,7 @@ def assert_compiled_select(self, text, expected_ast):
self.assertEqual(expected_ast, ast)

def assert_compile_error(self, text):
self.assertRaises(compiler.CompileError, compiler.compile_text,
self.assertRaises(exceptions.CompileError, compiler.compile_text,
text, self.tables_by_name)

def make_type_context(self, table_column_type_triples,
Expand Down Expand Up @@ -178,7 +181,7 @@ def test_unary_operator(self):
)

def test_mistyped_unary_operator(self):
with self.assertRaises(compiler.CompileError) as context:
with self.assertRaises(exceptions.CompileError) as context:
compiler.compile_text('SELECT -strings FROM rainbow_table',
self.tables_by_name)
self.assertTrue('Invalid type for operator' in str(context.exception))
Expand All @@ -187,12 +190,12 @@ def test_strange_arithmetic(self):
try:
compiler.compile_text('SELECT times + ints + floats + bools FROM '
'rainbow_table', self.tables_by_name)
except compiler.CompileError:
except exceptions.CompileError:
self.fail('Compiler exception on arithmetic across all numeric '
'types.')

def test_mistyped_binary_operator(self):
with self.assertRaises(compiler.CompileError) as context:
with self.assertRaises(exceptions.CompileError) as context:
compiler.compile_text('SELECT ints CONTAINS strings FROM '
'rainbow_table',
self.tables_by_name)
Expand Down Expand Up @@ -241,7 +244,7 @@ def test_function_calls(self):
)

def test_mistyped_function_call(self):
with self.assertRaises(compiler.CompileError) as context:
with self.assertRaises(exceptions.CompileError) as context:
compiler.compile_text('SELECT SUM(strings) FROM rainbow_table',
self.tables_by_name)
self.assertTrue('Invalid types for function' in str(context.exception))
Expand Down Expand Up @@ -1001,7 +1004,7 @@ def test_within_clause(self):
self.make_type_context([]))))

def test_within_clause_error(self):
with self.assertRaises(compiler.CompileError) as context:
with self.assertRaises(exceptions.CompileError) as context:
compiler.compile_text(
'SELECT r1.s, COUNT(r1.s) WITHIN r2 AS '
'num_s_in_r1 FROM record_table',
Expand Down
Loading