Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
SELECT
"t1"."x",
"t1"."y",
"t1"."z",
CASE WHEN "t1"."y" = "t1"."z" THEN 'big' ELSE 'small' END AS "size"
FROM (
SELECT
"t0"."x",
RANDOM() AS "y",
RANDOM() AS "z"
FROM "t" AS "t0"
) AS "t1"
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
SELECT
`t1`.`x`,
`t1`.`y`,
`t1`.`z`,
IF(`t1`.`y` = `t1`.`z`, 'big', 'small') AS `size`
FROM (
SELECT
`t0`.`x`,
RAND() AS `y`,
RAND() AS `z`
FROM `t` AS `t0`
) AS `t1`
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
SELECT
`t1`.`x`,
`t1`.`y`,
`t1`.`z`,
IF(`t1`.`y` = `t1`.`z`, 'big', 'small') AS `size`
FROM (
SELECT
`t0`.`x`,
UUID() AS `y`,
UUID() AS `z`
FROM `t` AS `t0`
) AS `t1`
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
SELECT
`t1`.`x`,
`t1`.`y`,
`t1`.`z`,
IF(`t1`.`y` = `t1`.`z`, 'big', 'small') AS `size`
FROM (
SELECT
`t0`.`x`,
RAND(UTC_TO_UNIX_MICROS(UTC_TIMESTAMP())) AS `y`,
RAND(UTC_TO_UNIX_MICROS(UTC_TIMESTAMP())) AS `z`
FROM `t` AS `t0`
) AS `t1`
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
SELECT
`t1`.`x`,
`t1`.`y`,
`t1`.`z`,
IF(`t1`.`y` = `t1`.`z`, 'big', 'small') AS `size`
FROM (
SELECT
`t0`.`x`,
UUID() AS `y`,
UUID() AS `z`
FROM `t` AS `t0`
) AS `t1`
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
SELECT
[t1].[x],
[t1].[y],
[t1].[z],
IIF([t1].[y] = [t1].[z], 'big', 'small') AS [size]
FROM (
SELECT
[t0].[x],
RAND() AS [y],
RAND() AS [z]
FROM [t] AS [t0]
) AS [t1]
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
SELECT
[t1].[x],
[t1].[y],
[t1].[z],
IIF([t1].[y] = [t1].[z], 'big', 'small') AS [size]
FROM (
SELECT
[t0].[x],
NEWID() AS [y],
NEWID() AS [z]
FROM [t] AS [t0]
) AS [t1]
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
SELECT
`t1`.`x`,
`t1`.`y`,
`t1`.`z`,
CASE WHEN `t1`.`y` = `t1`.`z` THEN 'big' ELSE 'small' END AS `size`
FROM (
SELECT
`t0`.`x`,
RAND() AS `y`,
RAND() AS `z`
FROM `t` AS `t0`
) AS `t1`
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
SELECT
`t1`.`x`,
`t1`.`y`,
`t1`.`z`,
CASE WHEN `t1`.`y` = `t1`.`z` THEN 'big' ELSE 'small' END AS `size`
FROM (
SELECT
`t0`.`x`,
UUID() AS `y`,
UUID() AS `z`
FROM `t` AS `t0`
) AS `t1`
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
SELECT
"t1"."x",
"t1"."y",
"t1"."z",
CASE WHEN "t1"."y" = "t1"."z" THEN 'big' ELSE 'small' END AS "size"
FROM (
SELECT
"t0"."x",
DBMS_RANDOM.VALUE() AS "y",
DBMS_RANDOM.VALUE() AS "z"
FROM "t" "t0"
) "t1"
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
SELECT
"t1"."x",
"t1"."y",
"t1"."z",
CASE WHEN "t1"."y" = "t1"."z" THEN 'big' ELSE 'small' END AS "size"
FROM (
SELECT
"t0"."x",
RANDOM() AS "y",
RANDOM() AS "z"
FROM "t" AS "t0"
) AS "t1"
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
SELECT
"t1"."x",
"t1"."y",
"t1"."z",
CASE WHEN "t1"."y" = "t1"."z" THEN 'big' ELSE 'small' END AS "size"
FROM (
SELECT
"t0"."x",
GEN_RANDOM_UUID() AS "y",
GEN_RANDOM_UUID() AS "z"
FROM "t" AS "t0"
) AS "t1"
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
SELECT
`t1`.`x`,
`t1`.`y`,
`t1`.`z`,
IF(`t1`.`y` = `t1`.`z`, 'big', 'small') AS `size`
FROM (
SELECT
`t0`.`x`,
RAND() AS `y`,
RAND() AS `z`
FROM `t` AS `t0`
) AS `t1`
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
SELECT
`t1`.`x`,
`t1`.`y`,
`t1`.`z`,
IF(`t1`.`y` = `t1`.`z`, 'big', 'small') AS `size`
FROM (
SELECT
`t0`.`x`,
UUID() AS `y`,
UUID() AS `z`
FROM `t` AS `t0`
) AS `t1`
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
SELECT
"t1"."x",
"t1"."y",
"t1"."z",
CASE WHEN "t1"."y" = "t1"."z" THEN 'big' ELSE 'small' END AS "size"
FROM (
SELECT
"t0"."x",
RANDOM() AS "y",
RANDOM() AS "z"
FROM "t" AS "t0"
) AS "t1"
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
SELECT
"t1"."x",
"t1"."y",
"t1"."z",
IFF("t1"."y" = "t1"."z", 'big', 'small') AS "size"
FROM (
SELECT
"t0"."x",
UNIFORM(TO_DOUBLE(0.0), TO_DOUBLE(1.0), RANDOM()) AS "y",
UNIFORM(TO_DOUBLE(0.0), TO_DOUBLE(1.0), RANDOM()) AS "z"
FROM "t" AS "t0"
) AS "t1"
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
SELECT
"t1"."x",
"t1"."y",
"t1"."z",
IFF("t1"."y" = "t1"."z", 'big', 'small') AS "size"
FROM (
SELECT
"t0"."x",
UUID_STRING() AS "y",
UUID_STRING() AS "z"
FROM "t" AS "t0"
) AS "t1"
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
SELECT
"t1"."x",
"t1"."y",
"t1"."z",
IIF("t1"."y" = "t1"."z", 'big', 'small') AS "size"
FROM (
SELECT
"t0"."x",
0.5 + (
CAST(RANDOM() AS REAL) / -1.8446744073709552e+19
) AS "y",
0.5 + (
CAST(RANDOM() AS REAL) / -1.8446744073709552e+19
) AS "z"
FROM "t" AS "t0"
) AS "t1"
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
SELECT
"t1"."x",
"t1"."y",
"t1"."z",
IIF("t1"."y" = "t1"."z", 'big', 'small') AS "size"
FROM (
SELECT
"t0"."x",
UUID() AS "y",
UUID() AS "z"
FROM "t" AS "t0"
) AS "t1"
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
SELECT
"t1"."x",
"t1"."y",
"t1"."z",
IF("t1"."y" = "t1"."z", 'big', 'small') AS "size"
FROM (
SELECT
"t0"."x",
RAND() AS "y",
RAND() AS "z"
FROM "t" AS "t0"
) AS "t1"
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
SELECT
"t1"."x",
"t1"."y",
"t1"."z",
IF("t1"."y" = "t1"."z", 'big', 'small') AS "size"
FROM (
SELECT
"t0"."x",
UUID() AS "y",
UUID() AS "z"
FROM "t" AS "t0"
) AS "t1"
28 changes: 28 additions & 0 deletions ibis/backends/tests/test_sql.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,3 +177,31 @@ def test_union_generates_predictable_aliases(con):
expr = ibis.union(sub1, sub2)
df = con.execute(expr)
assert len(df) == 2


@pytest.mark.never(
["pandas", "dask", "polars"],
reason="not SQL",
raises=NotImplementedError,
)
@pytest.mark.parametrize(
"value",
[
param(ibis.random(), id="random"),
param(
ibis.uuid(),
marks=pytest.mark.notimpl(
["exasol", "risingwave", "druid", "oracle", "pyspark"],
raises=exc.OperationNotDefinedError,
),
id="uuid",
),
],
)
def test_selects_with_impure_operations_not_merged(con, snapshot, value):
t = ibis.table({"x": "int64", "y": "float64"}, name="t")
t = t.mutate(y=value, z=value)
t = t.mutate(size=(t.y == t.z).ifelse("big", "small"))

sql = str(ibis.to_sql(t, dialect=con.name))
snapshot.assert_match(sql, "out.sql")
25 changes: 5 additions & 20 deletions ibis/backends/tests/test_uuid.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,37 +42,22 @@ def test_uuid_literal(con, backend):


@pytest.mark.notimpl(
[
"druid",
"exasol",
"mysql",
"oracle",
"polars",
"pyspark",
"risingwave",
],
["druid", "exasol", "oracle", "polars", "pyspark", "risingwave", "pandas", "dask"],
raises=com.OperationNotDefinedError,
)
@pytest.mark.notimpl(["pandas", "dask"], raises=ValueError)
@pytest.mark.broken(
["mysql"], raises=AssertionError, reason="MySQL generates version 1 UUIDs"
)
def test_uuid_function(con):
obj = con.execute(ibis.uuid())
assert isinstance(obj, uuid.UUID)
assert obj.version == 4


@pytest.mark.notimpl(
[
"druid",
"exasol",
"mysql",
"oracle",
"polars",
"pyspark",
"risingwave",
],
["druid", "exasol", "oracle", "polars", "pyspark", "risingwave", "pandas", "dask"],
raises=com.OperationNotDefinedError,
)
@pytest.mark.notimpl(["pandas", "dask"], raises=ValueError)
def test_uuid_unique_each_row(con):
expr = (
con.tables.functional_alltypes.mutate(uuid=ibis.uuid()).limit(2).uuid.nunique()
Expand Down
1 change: 0 additions & 1 deletion ibis/backends/trino/compiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,6 @@ class TrinoCompiler(SQLGlotCompiler):
ops.ExtractPath: "url_extract_path",
ops.ExtractFragment: "url_extract_fragment",
ops.ArrayPosition: "array_position",
ops.RandomUUID: "uuid",
}

def _aggregate(self, funcname: str, *args, where):
Expand Down
17 changes: 15 additions & 2 deletions ibis/expr/operations/generic.py
Original file line number Diff line number Diff line change
Expand Up @@ -183,6 +183,17 @@ class Constant(Scalar, Singleton):
shape = ds.scalar


@public
class Impure(Value):
_counter = itertools.count()
uid: Optional[int] = None

def __init__(self, uid, **kwargs):
if uid is None:
uid = next(self._counter)
super().__init__(uid=uid, **kwargs)


@public
class TimestampNow(Constant):
dtype = dt.timestamp
Expand All @@ -194,13 +205,15 @@ class DateNow(Constant):


@public
class RandomScalar(Constant):
class RandomScalar(Impure):
dtype = dt.float64
shape = ds.scalar


@public
class RandomUUID(Constant):
class RandomUUID(Impure):
dtype = dt.uuid
shape = ds.scalar


@public
Expand Down
10 changes: 10 additions & 0 deletions ibis/expr/operations/tests/test_generic.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,3 +143,13 @@ def test_NULL():
assert ops.NULL.dtype is dt.null
assert ops.NULL == ops.Literal(None, dt.null)
assert ops.NULL is not ops.Literal(None, dt.int8)


@pytest.mark.parametrize("op", [ops.RandomScalar, ops.RandomUUID])
def test_unique_impure_values(op):
assert op() != op()
assert hash(op()) != hash(op())

node = op()
other = node.copy()
assert node == other
25 changes: 25 additions & 0 deletions ibis/expr/tests/test_newrels.py
Original file line number Diff line number Diff line change
Expand Up @@ -1606,3 +1606,28 @@ def test_subsequent_order_by_calls():
first = ops.Sort(t, [t.int_col.desc()]).to_expr()
second = ops.Sort(first, [first.int_col.asc()]).to_expr()
assert ts.equals(second)


@pytest.mark.parametrize("func", [ibis.random, ibis.uuid])
def test_impure_operation_dereferencing(func):
t = ibis.table({"x": "int64"}, name="t")

impure = func()
t1 = t.mutate(y=impure)
t2 = t1.mutate(z=impure.cast("string"))

expected = ops.Project(
parent=t1,
values={"x": t1.x, "y": t1.y, "z": t1.y.cast("string")},
)
assert t2.op() == expected

v1 = func()
v2 = func()

t1 = t.mutate(y=v1)
t2 = t1.mutate(z=v2.cast("string"))
expected = ops.Project(
parent=t1, values={"x": t1.x, "y": t1.y, "z": v2.cast("string")}
)
assert t2.op() == expected
6 changes: 5 additions & 1 deletion ibis/tests/expr/test_table.py
Original file line number Diff line number Diff line change
Expand Up @@ -567,11 +567,15 @@ def test_order_by_asc_deferred_sort_key(table):
assert_equal(result, expected2)


# different instantiations create unique objects
rand = ibis.random()


@pytest.mark.parametrize(
("key", "expected"),
[
param(ibis.NA, ibis.NA.op(), id="na"),
param(ibis.random(), ibis.random().op(), id="random"),
param(rand, rand.op(), id="random"),
param(1.0, ibis.literal(1.0).op(), id="float"),
param(ibis.literal("a"), ibis.literal("a").op(), id="string"),
param(ibis.literal([1, 2, 3]), ibis.literal([1, 2, 3]).op(), id="array"),
Expand Down
5 changes: 3 additions & 2 deletions ibis/tests/expr/test_window_frames.py
Original file line number Diff line number Diff line change
Expand Up @@ -246,15 +246,16 @@ def test_window_api_supports_scalar_order_by(t):
)
assert expr == expected

window = ibis.window(order_by=ibis.random())
rand = ibis.random()
window = ibis.window(order_by=rand)
expr = t.a.sum().over(window).op()
expected = ops.WindowFunction(
t.a.sum(),
how="rows",
start=None,
end=None,
group_by=(),
order_by=[ibis.random()],
order_by=[rand],
)
assert expr == expected

Expand Down