This notebook will show you how to add a new reduction operation (bitwise_and
) to an existing backend (PostgreSQL).
A reduction operation is a function that maps $N$ rows to 1 row, for example the sum
function.
Let's define the bitwise_and
operation as a function that takes any integer typed column as input and returns an integer
bitwise_and :: Column Int -> Int
In [ ]:
import ibis.expr.datatypes as dt
import ibis.expr.rules as rlz
from ibis.expr.operations import Reduction, Arg
class BitwiseAnd(Reduction):
arg = Arg(rlz.column(rlz.integer))
where = Arg(rlz.boolean, default=None)
output_type = rlz.scalar_like('arg')
We just defined a BitwiseAnd
class that takes one integer column as input, and returns a scalar output of the same type as the input. This matches both the requirements of a reduction and the spepcifics of the function that we want to implement.
Note: It is very important that you write the correct argument rules and output type here. The expression will not work otherwise.
Because every reduction in ibis has the ability to filter out values during aggregation (a typical feature in databases and analytics tools), to make an expression out of BitwiseAnd
we need to pass an additional argument: where
to our BitwiseAnd
constructor.
In [ ]:
from ibis.expr.types import IntegerColumn # not IntegerValue! reductions are only valid on columns
def bitwise_and(integer_column, where=None):
return BitwiseAnd(integer_column, where=where).to_expr()
IntegerColumn.bitwise_and = bitwise_and
In [ ]:
import ibis
In [ ]:
t = ibis.table([('bigint_col', 'int64'), ('string_col', 'string')], name='t')
In [ ]:
t.bigint_col.bitwise_and()
In [ ]:
t.bigint_col.bitwise_and(t.string_col == '1')
In [ ]:
import sqlalchemy as sa
@ibis.postgres.compiles(BitwiseAnd)
def compile_sha1(translator, expr):
# pull out the arguments to the expression
arg, where = expr.op().args
# compile the argument
compiled_arg = translator.translate(arg)
# call the appropriate postgres function
agg = sa.func.bit_and(compiled_arg)
# handle a non-None filter clause
if where is not None:
return agg.filter(translator.translate(where))
return agg
NOTE:
To be able to execute the rest of this notebook you need to run the following command from your ibis clone:
ci/build.sh
In [ ]:
con = ibis.postgres.connect(
user='postgres',
host='postgres',
password='postgres',
database='ibis_testing'
)
In [ ]:
t = con.table('functional_alltypes')
t
In [ ]:
expr = t.bigint_col.bitwise_and()
expr
In [ ]:
sql_expr = expr.compile()
print(sql_expr)
In [ ]:
expr.execute()
In [ ]:
expr = t.bigint_col.bitwise_and(where=(t.bigint_col == 10) | (t.bigint_col == 40))
expr
In [ ]:
result = expr.execute()
result
In [ ]:
10 & 40
In [ ]:
print(' {:0>8b}'.format(10))
print('& {:0>8b}'.format(40))
print('-' * 10)
print(' {:0>8b}'.format(10 & 40))